blob: 883182b20ccf3437e7cc706008996374645f7d2f [file] [log] [blame]
Charles Chan188ebf52015-12-23 00:15:11 -08001package org.onosproject.driver.pipeline;
2
Charles Chane849c192016-01-11 18:28:54 -08003import com.google.common.base.Objects;
Charles Chan188ebf52015-12-23 00:15:11 -08004import com.google.common.cache.Cache;
5import com.google.common.cache.CacheBuilder;
6import com.google.common.cache.RemovalCause;
7import com.google.common.cache.RemovalNotification;
8import org.onlab.osgi.ServiceDirectory;
Charles Chan5270ed02016-01-30 23:22:37 -08009import org.onlab.packet.MacAddress;
Charles Chan188ebf52015-12-23 00:15:11 -080010import org.onlab.packet.MplsLabel;
11import org.onlab.packet.VlanId;
12import org.onosproject.core.ApplicationId;
13import org.onosproject.core.DefaultGroupId;
14import org.onosproject.net.DeviceId;
15import org.onosproject.net.PortNumber;
16import org.onosproject.net.behaviour.NextGroup;
17import org.onosproject.net.behaviour.PipelinerContext;
18import org.onosproject.net.flow.DefaultTrafficTreatment;
19import org.onosproject.net.flow.TrafficSelector;
20import org.onosproject.net.flow.TrafficTreatment;
21import org.onosproject.net.flow.criteria.Criterion;
22import org.onosproject.net.flow.criteria.VlanIdCriterion;
23import org.onosproject.net.flow.instructions.Instruction;
24import org.onosproject.net.flow.instructions.Instructions;
25import org.onosproject.net.flow.instructions.L2ModificationInstruction;
26import org.onosproject.net.flowobjective.FlowObjectiveStore;
27import org.onosproject.net.flowobjective.NextObjective;
28import org.onosproject.net.flowobjective.ObjectiveError;
29import org.onosproject.net.group.DefaultGroupBucket;
30import org.onosproject.net.group.DefaultGroupDescription;
31import org.onosproject.net.group.DefaultGroupKey;
32import org.onosproject.net.group.Group;
33import org.onosproject.net.group.GroupBucket;
34import org.onosproject.net.group.GroupBuckets;
35import org.onosproject.net.group.GroupDescription;
36import org.onosproject.net.group.GroupEvent;
37import org.onosproject.net.group.GroupKey;
38import org.onosproject.net.group.GroupListener;
39import org.onosproject.net.group.GroupService;
40import org.slf4j.Logger;
41
42import java.util.ArrayDeque;
43import java.util.ArrayList;
44import java.util.Collection;
45import java.util.Collections;
46import java.util.Deque;
47import java.util.List;
48import java.util.Map;
49import 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 Chan5270ed02016-01-30 23:22:37 -0800336 MacAddress dstMac = MacAddress.ZERO;
Charles Chan188ebf52015-12-23 00:15:11 -0800337 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:
Charles Chan5270ed02016-01-30 23:22:37 -0800342 dstMac = ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac();
343 outerTtb.setEthDst(dstMac);
Charles Chan188ebf52015-12-23 00:15:11 -0800344 break;
345 case ETH_SRC:
346 outerTtb.setEthSrc(((L2ModificationInstruction.ModEtherInstruction) l2ins).mac());
347 break;
348 case VLAN_ID:
349 vlanid = ((L2ModificationInstruction.ModVlanIdInstruction) l2ins).vlanId();
350 outerTtb.setVlanId(vlanid);
351 setVlan = true;
352 break;
353 case VLAN_POP:
354 innerTtb.popVlan();
355 popVlan = true;
356 break;
357 case DEC_MPLS_TTL:
358 case MPLS_LABEL:
359 case MPLS_POP:
360 case MPLS_PUSH:
361 case VLAN_PCP:
362 case VLAN_PUSH:
363 default:
364 break;
365 }
366 } else if (ins.type() == Instruction.Type.OUTPUT) {
367 portNum = ((Instructions.OutputInstruction) ins).port().toLong();
368 innerTtb.add(ins);
369 } else {
370 log.warn("Driver does not handle this type of TrafficTreatment"
371 + " instruction in nextObjectives: {}", ins.type());
372 }
373 }
374
375 if (vlanid == null && meta != null) {
376 // use metadata if available
377 Criterion vidCriterion = meta.getCriterion(Criterion.Type.VLAN_VID);
378 if (vidCriterion != null) {
379 vlanid = ((VlanIdCriterion) vidCriterion).vlanId();
380 }
381 // if vlan is not set, use the vlan in metadata for outerTtb
382 if (vlanid != null && !setVlan) {
383 outerTtb.setVlanId(vlanid);
384 }
385 }
386
387 if (vlanid == null) {
388 log.error("Driver cannot process an L2/L3 group chain without "
389 + "egress vlan information for dev: {} port:{}",
390 deviceId, portNum);
391 return null;
392 }
393
394 if (!setVlan && !popVlan) {
395 // untagged outgoing port
396 TrafficTreatment.Builder temp = DefaultTrafficTreatment.builder();
397 temp.popVlan();
398 innerTtb.build().allInstructions().forEach(i -> temp.add(i));
399 innerTtb = temp;
400 }
401
402 // assemble information for ofdpa l2interface group
Charles Chane849c192016-01-11 18:28:54 -0800403 int l2groupId = L2_INTERFACE_TYPE | (vlanid.toShort() << 16) | (int) portNum;
Charles Chan188ebf52015-12-23 00:15:11 -0800404 // a globally unique groupkey that is different for ports in the same devices
405 // but different for the same portnumber on different devices. Also different
406 // for the various group-types created out of the same next objective.
Charles Chane849c192016-01-11 18:28:54 -0800407 int l2gk = l2InterfaceGroupKey(deviceId, vlanid, portNum);
Charles Chan188ebf52015-12-23 00:15:11 -0800408 final GroupKey l2groupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l2gk));
409
410 // assemble information for outer group
411 GroupDescription outerGrpDesc = null;
412 if (mpls) {
413 // outer group is MPLSInteface
Charles Chane849c192016-01-11 18:28:54 -0800414 int mplsgroupId = MPLS_INTERFACE_TYPE | (int) portNum;
Charles Chan188ebf52015-12-23 00:15:11 -0800415 // using mplsinterfacemask in groupkey to differentiate from l2interface
Charles Chane849c192016-01-11 18:28:54 -0800416 int mplsgk = MPLS_INTERFACE_TYPE | (SUBTYPE_MASK & (deviceId.hashCode() << 8 | (int) portNum));
Charles Chan188ebf52015-12-23 00:15:11 -0800417 final GroupKey mplsgroupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(mplsgk));
418 outerTtb.group(new DefaultGroupId(l2groupId));
419 // create the mpls-interface group description to wait for the
420 // l2 interface group to be processed
421 GroupBucket mplsinterfaceGroupBucket =
422 DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
423 outerGrpDesc = new DefaultGroupDescription(
424 deviceId,
425 GroupDescription.Type.INDIRECT,
426 new GroupBuckets(Collections.singletonList(
427 mplsinterfaceGroupBucket)),
428 mplsgroupkey,
429 mplsgroupId,
430 appId);
431 log.debug("Trying MPLS-Interface: device:{} gid:{} gkey:{} nextid:{}",
432 deviceId, Integer.toHexString(mplsgroupId),
433 mplsgroupkey, nextId);
434 } else {
435 // outer group is L3Unicast
Charles Chan5270ed02016-01-30 23:22:37 -0800436 int l3groupId = L3_UNICAST_TYPE |
437 (TYPE_MASK & (int) (dstMac.toLong() & 0xffff) << 6 | (int) portNum);
438 int l3gk = L3_UNICAST_TYPE |
439 (TYPE_MASK & (deviceId.hashCode() << 22 |
440 (int) (dstMac.toLong() & 0xffff) << 6 | (int) portNum));
Charles Chan188ebf52015-12-23 00:15:11 -0800441 final GroupKey l3groupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l3gk));
442 outerTtb.group(new DefaultGroupId(l2groupId));
443 // create the l3unicast group description to wait for the
444 // l2 interface group to be processed
445 GroupBucket l3unicastGroupBucket =
446 DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
447 outerGrpDesc = new DefaultGroupDescription(
448 deviceId,
449 GroupDescription.Type.INDIRECT,
450 new GroupBuckets(Collections.singletonList(
451 l3unicastGroupBucket)),
452 l3groupkey,
453 l3groupId,
454 appId);
455 log.debug("Trying L3Unicast: device:{} gid:{} gkey:{} nextid:{}",
456 deviceId, Integer.toHexString(l3groupId),
457 l3groupkey, nextId);
458 }
459
460 // store l2groupkey with the groupChainElem for the outer-group that depends on it
461 GroupChainElem gce = new GroupChainElem(outerGrpDesc, 1, false);
462 updatePendingGroups(l2groupkey, gce);
463
464 // create group description for the inner l2interfacegroup
465 GroupBucket l2interfaceGroupBucket =
466 DefaultGroupBucket.createIndirectGroupBucket(innerTtb.build());
467 GroupDescription l2groupDescription =
468 new DefaultGroupDescription(
469 deviceId,
470 GroupDescription.Type.INDIRECT,
471 new GroupBuckets(Collections.singletonList(
472 l2interfaceGroupBucket)),
473 l2groupkey,
474 l2groupId,
475 appId);
476 log.debug("Trying L2Interface: device:{} gid:{} gkey:{} nextId:{}",
477 deviceId, Integer.toHexString(l2groupId),
478 l2groupkey, nextId);
479 return new GroupInfo(l2groupDescription, outerGrpDesc);
480
481 }
482
483 /**
484 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
485 * a chain of groups. The broadcast Next Objective passed in by the application
486 * has to be broken up into a group chain comprising of an
487 * L2 Flood group whose buckets point to L2 Interface groups.
488 *
489 * @param nextObj the nextObjective of type BROADCAST
490 */
491 private void processBroadcastNextObjective(NextObjective nextObj) {
492 // break up broadcast next objective to multiple groups
493 Collection<TrafficTreatment> buckets = nextObj.next();
494
Charles Chane849c192016-01-11 18:28:54 -0800495 VlanId vlanId = readVlanFromMeta(nextObj);
496 if (vlanId == null) {
Charles Chan188ebf52015-12-23 00:15:11 -0800497 log.warn("Required VLAN ID info in nextObj metadata but not found. Aborting");
498 return;
499 }
Charles Chan188ebf52015-12-23 00:15:11 -0800500
501 // each treatment is converted to an L2 interface group
502 List<GroupDescription> l2interfaceGroupDescs = new ArrayList<>();
503 List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
504 for (TrafficTreatment treatment : buckets) {
505 TrafficTreatment.Builder newTreatment = DefaultTrafficTreatment.builder();
506 PortNumber portNum = null;
507 // ensure that the only allowed treatments are pop-vlan and output
508 for (Instruction ins : treatment.allInstructions()) {
509 if (ins.type() == Instruction.Type.L2MODIFICATION) {
510 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
511 switch (l2ins.subtype()) {
512 case VLAN_POP:
513 newTreatment.add(l2ins);
514 break;
515 default:
516 log.debug("action {} not permitted for broadcast nextObj",
517 l2ins.subtype());
518 break;
519 }
520 } else if (ins.type() == Instruction.Type.OUTPUT) {
521 portNum = ((Instructions.OutputInstruction) ins).port();
522 newTreatment.add(ins);
523 } else {
Charles Chane849c192016-01-11 18:28:54 -0800524 log.debug("TrafficTreatment of type {} not permitted in " +
525 " broadcast nextObjective", ins.type());
Charles Chan188ebf52015-12-23 00:15:11 -0800526 }
527 }
528
Charles Chan188ebf52015-12-23 00:15:11 -0800529 // assemble info for l2 interface group
Charles Chane849c192016-01-11 18:28:54 -0800530 int l2gk = l2InterfaceGroupKey(deviceId, vlanId, portNum.toLong());
Charles Chan188ebf52015-12-23 00:15:11 -0800531 final GroupKey l2groupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l2gk));
Charles Chane849c192016-01-11 18:28:54 -0800532 int l2groupId = L2_INTERFACE_TYPE | (vlanId.toShort() << 16) |
Charles Chan188ebf52015-12-23 00:15:11 -0800533 (int) portNum.toLong();
534 GroupBucket l2interfaceGroupBucket =
535 DefaultGroupBucket.createIndirectGroupBucket(newTreatment.build());
536 GroupDescription l2interfaceGroupDescription =
537 new DefaultGroupDescription(
538 deviceId,
539 GroupDescription.Type.INDIRECT,
540 new GroupBuckets(Collections.singletonList(
541 l2interfaceGroupBucket)),
542 l2groupkey,
543 l2groupId,
544 nextObj.appId());
545 log.debug("Trying L2-Interface: device:{} gid:{} gkey:{} nextid:{}",
546 deviceId, Integer.toHexString(l2groupId),
547 l2groupkey, nextObj.id());
548
549 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
550 gkeyChain.addFirst(l2groupkey);
551
552 // store the info needed to create this group
553 l2interfaceGroupDescs.add(l2interfaceGroupDescription);
554 allGroupKeys.add(gkeyChain);
555 }
556
557 // assemble info for l2 flood group
Charles Chane849c192016-01-11 18:28:54 -0800558 Integer l2floodgroupId = L2_FLOOD_TYPE | (vlanId.toShort() << 16) | nextObj.id();
559 int l2floodgk = L2_FLOOD_TYPE | nextObj.id() << 12;
Charles Chan188ebf52015-12-23 00:15:11 -0800560 final GroupKey l2floodgroupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l2floodgk));
561 // collection of group buckets pointing to all the l2 interface groups
562 List<GroupBucket> l2floodBuckets = new ArrayList<>();
563 for (GroupDescription l2intGrpDesc : l2interfaceGroupDescs) {
564 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
565 ttb.group(new DefaultGroupId(l2intGrpDesc.givenGroupId()));
566 GroupBucket abucket = DefaultGroupBucket.createAllGroupBucket(ttb.build());
567 l2floodBuckets.add(abucket);
568 }
569 // create the l2flood group-description to wait for all the
570 // l2interface groups to be processed
571 GroupDescription l2floodGroupDescription =
572 new DefaultGroupDescription(
573 deviceId,
574 GroupDescription.Type.ALL,
575 new GroupBuckets(l2floodBuckets),
576 l2floodgroupkey,
577 l2floodgroupId,
578 nextObj.appId());
579 GroupChainElem gce = new GroupChainElem(l2floodGroupDescription,
580 l2interfaceGroupDescs.size(),
581 false);
582 log.debug("Trying L2-Flood: device:{} gid:{} gkey:{} nextid:{}",
583 deviceId, Integer.toHexString(l2floodgroupId),
584 l2floodgroupkey, nextObj.id());
585
586 // create objects for local and distributed storage
587 allGroupKeys.forEach(gkeyChain -> gkeyChain.addFirst(l2floodgroupkey));
588 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
589
590 // store l2floodgroupkey with the ofdpaGroupChain for the nextObjective
591 // that depends on it
592 updatePendingNextObjective(l2floodgroupkey, ofdpaGrp);
593
594 for (GroupDescription l2intGrpDesc : l2interfaceGroupDescs) {
595 // store all l2groupkeys with the groupChainElem for the l2floodgroup
596 // that depends on it
597 updatePendingGroups(l2intGrpDesc.appCookie(), gce);
598 // send groups for all l2 interface groups
599 groupService.addGroup(l2intGrpDesc);
600 }
601 }
602
Charles Chan188ebf52015-12-23 00:15:11 -0800603 /**
604 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
605 * a chain of groups. The hashed Next Objective passed in by the application
606 * has to be broken up into a group chain comprising of an
607 * L3 ECMP group as the top level group. Buckets of this group can point
608 * to a variety of groups in a group chain, depending on the whether
609 * MPLS labels are being pushed or not.
610 * <p>
611 * NOTE: We do not create MPLS ECMP groups as they are unimplemented in
612 * OF-DPA 2.0 (even though it is in the spec). Therefore we do not
613 * check the nextObjective meta to see what is matching before being
614 * sent to this nextObjective.
615 *
616 * @param nextObj the nextObjective of type HASHED
617 */
618 private void processHashedNextObjective(NextObjective nextObj) {
619 // storage for all group keys in the chain of groups created
620 List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
621 List<GroupInfo> unsentGroups = new ArrayList<>();
622 createHashBucketChains(nextObj, allGroupKeys, unsentGroups);
623
624 // now we can create the outermost L3 ECMP group
625 List<GroupBucket> l3ecmpGroupBuckets = new ArrayList<>();
626 for (GroupInfo gi : unsentGroups) {
627 // create ECMP bucket to point to the outer group
628 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
629 ttb.group(new DefaultGroupId(gi.outerGrpDesc.givenGroupId()));
630 GroupBucket sbucket = DefaultGroupBucket
631 .createSelectGroupBucket(ttb.build());
632 l3ecmpGroupBuckets.add(sbucket);
633 }
Charles Chane849c192016-01-11 18:28:54 -0800634 int l3ecmpGroupId = L3_ECMP_TYPE | nextObj.id() << 12;
Charles Chan188ebf52015-12-23 00:15:11 -0800635 GroupKey l3ecmpGroupKey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l3ecmpGroupId));
636 GroupDescription l3ecmpGroupDesc =
637 new DefaultGroupDescription(
638 deviceId,
639 GroupDescription.Type.SELECT,
640 new GroupBuckets(l3ecmpGroupBuckets),
641 l3ecmpGroupKey,
642 l3ecmpGroupId,
643 nextObj.appId());
644 GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc,
645 l3ecmpGroupBuckets.size(),
646 false);
647
648 // create objects for local and distributed storage
649 allGroupKeys.forEach(gkeyChain -> gkeyChain.addFirst(l3ecmpGroupKey));
650 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
651
652 // store l3ecmpGroupKey with the ofdpaGroupChain for the nextObjective
653 // that depends on it
654 updatePendingNextObjective(l3ecmpGroupKey, ofdpaGrp);
655
656 log.debug("Trying L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
657 deviceId, Integer.toHexString(l3ecmpGroupId),
658 l3ecmpGroupKey, nextObj.id());
659 // finally we are ready to send the innermost groups
660 for (GroupInfo gi : unsentGroups) {
661 log.debug("Sending innermost group {} in group chain on device {} ",
662 Integer.toHexString(gi.innerGrpDesc.givenGroupId()), deviceId);
663 updatePendingGroups(gi.outerGrpDesc.appCookie(), l3ecmpGce);
664 groupService.addGroup(gi.innerGrpDesc);
665 }
666
667 }
668
669 /**
670 * Creates group chains for all buckets in a hashed group, and stores the
671 * GroupInfos and GroupKeys for all the groups in the lists passed in, which
672 * should be empty.
673 * <p>
674 * Does not create the top level ECMP group. Does not actually send the
675 * groups to the groupService.
676 *
677 * @param nextObj the Next Objective with buckets that need to be converted
678 * to group chains
679 * @param allGroupKeys a list to store groupKey for each bucket-group-chain
680 * @param unsentGroups a list to store GroupInfo for each bucket-group-chain
681 */
682 private void createHashBucketChains(NextObjective nextObj,
683 List<Deque<GroupKey>> allGroupKeys,
684 List<GroupInfo> unsentGroups) {
685 // break up hashed next objective to multiple groups
686 Collection<TrafficTreatment> buckets = nextObj.next();
687
688 for (TrafficTreatment bucket : buckets) {
689 //figure out how many labels are pushed in each bucket
690 int labelsPushed = 0;
691 MplsLabel innermostLabel = null;
692 for (Instruction ins : bucket.allInstructions()) {
693 if (ins.type() == Instruction.Type.L2MODIFICATION) {
694 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
695 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.MPLS_PUSH) {
696 labelsPushed++;
697 }
698 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.MPLS_LABEL) {
699 if (innermostLabel == null) {
700 innermostLabel = ((L2ModificationInstruction.ModMplsLabelInstruction) l2ins).mplsLabel();
701 }
702 }
703 }
704 }
705
706 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
707 // XXX we only deal with 0 and 1 label push right now
708 if (labelsPushed == 0) {
709 GroupInfo nolabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
710 nextObj.appId(), false,
711 nextObj.meta());
712 if (nolabelGroupInfo == null) {
713 log.error("Could not process nextObj={} in dev:{}",
714 nextObj.id(), deviceId);
715 return;
716 }
717 gkeyChain.addFirst(nolabelGroupInfo.innerGrpDesc.appCookie());
718 gkeyChain.addFirst(nolabelGroupInfo.outerGrpDesc.appCookie());
719
720 // we can't send the inner group description yet, as we have to
721 // create the dependent ECMP group first. So we store..
722 unsentGroups.add(nolabelGroupInfo);
723
724 } else if (labelsPushed == 1) {
725 GroupInfo onelabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
726 nextObj.appId(), true,
727 nextObj.meta());
728 if (onelabelGroupInfo == null) {
729 log.error("Could not process nextObj={} in dev:{}",
730 nextObj.id(), deviceId);
731 return;
732 }
733 // we need to add another group to this chain - the L3VPN group
734 TrafficTreatment.Builder l3vpnTtb = DefaultTrafficTreatment.builder();
735 l3vpnTtb.pushMpls()
736 .setMpls(innermostLabel)
737 .setMplsBos(true)
738 .copyTtlOut()
739 .group(new DefaultGroupId(
740 onelabelGroupInfo.outerGrpDesc.givenGroupId()));
741 GroupBucket l3vpnGrpBkt =
742 DefaultGroupBucket.createIndirectGroupBucket(l3vpnTtb.build());
Charles Chan5270ed02016-01-30 23:22:37 -0800743 int l3vpngroupId = MPLS_L3VPN_SUBTYPE | l3VpnIndex.incrementAndGet();
744 int l3vpngk = MPLS_L3VPN_SUBTYPE | nextObj.id() << 12 | l3VpnIndex.get();
Charles Chan188ebf52015-12-23 00:15:11 -0800745 GroupKey l3vpngroupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l3vpngk));
746 GroupDescription l3vpnGroupDesc =
747 new DefaultGroupDescription(
748 deviceId,
749 GroupDescription.Type.INDIRECT,
750 new GroupBuckets(Collections.singletonList(
751 l3vpnGrpBkt)),
752 l3vpngroupkey,
753 l3vpngroupId,
754 nextObj.appId());
755 GroupChainElem l3vpnGce = new GroupChainElem(l3vpnGroupDesc, 1, false);
756 updatePendingGroups(onelabelGroupInfo.outerGrpDesc.appCookie(), l3vpnGce);
757
758 gkeyChain.addFirst(onelabelGroupInfo.innerGrpDesc.appCookie());
759 gkeyChain.addFirst(onelabelGroupInfo.outerGrpDesc.appCookie());
760 gkeyChain.addFirst(l3vpngroupkey);
761
762 //now we can replace the outerGrpDesc with the one we just created
763 onelabelGroupInfo.outerGrpDesc = l3vpnGroupDesc;
764
765 // we can't send the innermost group yet, as we have to create
766 // the dependent ECMP group first. So we store ...
767 unsentGroups.add(onelabelGroupInfo);
768
769 log.debug("Trying L3VPN: device:{} gid:{} gkey:{} nextId:{}",
770 deviceId, Integer.toHexString(l3vpngroupId),
771 l3vpngroupkey, nextObj.id());
772
773 } else {
774 log.warn("Driver currently does not handle more than 1 MPLS "
775 + "labels. Not processing nextObjective {}", nextObj.id());
776 return;
777 }
778
779 // all groups in this chain
780 allGroupKeys.add(gkeyChain);
781 }
782 }
783
784 /**
785 * Adds a bucket to the top level group of a group-chain, and creates the chain.
786 *
787 * @param nextObjective the next group to add a bucket to
788 * @param next the representation of the existing group-chain for this next objective
789 */
790 protected void addBucketToGroup(NextObjective nextObjective, NextGroup next) {
791 if (nextObjective.type() != NextObjective.Type.HASHED) {
792 log.warn("AddBuckets not applied to nextType:{} in dev:{} for next:{}",
793 nextObjective.type(), deviceId, nextObjective.id());
794 return;
795 }
796 if (nextObjective.next().size() > 1) {
797 log.warn("Only one bucket can be added at a time");
798 return;
799 }
800 // storage for all group keys in the chain of groups created
801 List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
802 List<GroupInfo> unsentGroups = new ArrayList<>();
803 createHashBucketChains(nextObjective, allGroupKeys, unsentGroups);
804
805 // now we can create the outermost L3 ECMP group bucket to add
806 GroupInfo gi = unsentGroups.get(0); // only one bucket, so only one group-chain
807 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
808 ttb.group(new DefaultGroupId(gi.outerGrpDesc.givenGroupId()));
809 GroupBucket sbucket = DefaultGroupBucket.createSelectGroupBucket(ttb.build());
810
811 // recreate the original L3 ECMP group id and description
Charles Chane849c192016-01-11 18:28:54 -0800812 int l3ecmpGroupId = L3_ECMP_TYPE | nextObjective.id() << 12;
Charles Chan188ebf52015-12-23 00:15:11 -0800813 GroupKey l3ecmpGroupKey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l3ecmpGroupId));
814
815 // Although GroupDescriptions are not necessary for adding buckets to
816 // existing groups, we use one in the GroupChainElem. When the latter is
817 // processed, the info will be extracted for the bucketAdd call to groupService
818 GroupDescription l3ecmpGroupDesc =
819 new DefaultGroupDescription(
820 deviceId,
821 GroupDescription.Type.SELECT,
822 new GroupBuckets(Collections.singletonList(sbucket)),
823 l3ecmpGroupKey,
824 l3ecmpGroupId,
825 nextObjective.appId());
826 GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc, 1, true);
827
828 // update original NextGroup with new bucket-chain
829 // don't need to update pendingNextObjectives -- group already exists
830 Deque<GroupKey> newBucketChain = allGroupKeys.get(0);
831 newBucketChain.addFirst(l3ecmpGroupKey);
832 List<Deque<GroupKey>> allOriginalKeys = OFDPA2Pipeline.appKryo.deserialize(next.data());
833 allOriginalKeys.add(newBucketChain);
834 flowObjectiveStore.putNextGroup(nextObjective.id(),
835 new OfdpaNextGroup(allOriginalKeys, nextObjective));
836
837 log.debug("Adding to L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
838 deviceId, Integer.toHexString(l3ecmpGroupId),
839 l3ecmpGroupKey, nextObjective.id());
840 // send the innermost group
841 log.debug("Sending innermost group {} in group chain on device {} ",
842 Integer.toHexString(gi.innerGrpDesc.givenGroupId()), deviceId);
843 updatePendingGroups(gi.outerGrpDesc.appCookie(), l3ecmpGce);
844 groupService.addGroup(gi.innerGrpDesc);
845
846 }
847
848 /**
849 * Removes the bucket in the top level group of a possible group-chain. Does
850 * not remove the groups in a group-chain pointed to by this bucket, as they
851 * may be in use (referenced by other groups) elsewhere.
852 *
853 * @param nextObjective the next group to remove a bucket from
854 * @param next the representation of the existing group-chain for this next objective
855 */
856 protected void removeBucketFromGroup(NextObjective nextObjective, NextGroup next) {
857 if (nextObjective.type() != NextObjective.Type.HASHED) {
858 log.warn("RemoveBuckets not applied to nextType:{} in dev:{} for next:{}",
859 nextObjective.type(), deviceId, nextObjective.id());
860 return;
861 }
862 Collection<TrafficTreatment> treatments = nextObjective.next();
863 TrafficTreatment treatment = treatments.iterator().next();
864 // find the bucket to remove by noting the outport, and figuring out the
865 // top-level group in the group-chain that indirectly references the port
866 PortNumber outport = null;
867 for (Instruction ins : treatment.allInstructions()) {
868 if (ins instanceof Instructions.OutputInstruction) {
869 outport = ((Instructions.OutputInstruction) ins).port();
870 break;
871 }
872 }
873 if (outport == null) {
874 log.error("next objective {} has no outport", nextObjective.id());
875 return;
876 }
877
878 List<Deque<GroupKey>> allgkeys = OFDPA2Pipeline.appKryo.deserialize(next.data());
879 Deque<GroupKey> foundChain = null;
880 int index = 0;
881 for (Deque<GroupKey> gkeys : allgkeys) {
882 GroupKey groupWithPort = gkeys.peekLast();
883 Group group = groupService.getGroup(deviceId, groupWithPort);
884 if (group == null) {
885 log.warn("Inconsistent group chain");
886 continue;
887 }
888 // last group in group chain should have a single bucket pointing to port
889 List<Instruction> lastIns = group.buckets().buckets().iterator()
890 .next().treatment().allInstructions();
891 for (Instruction i : lastIns) {
892 if (i instanceof Instructions.OutputInstruction) {
893 PortNumber lastport = ((Instructions.OutputInstruction) i).port();
894 if (lastport.equals(outport)) {
895 foundChain = gkeys;
896 break;
897 }
898 }
899 }
900 if (foundChain != null) {
901 break;
902 }
903 index++;
904 }
905 if (foundChain != null) {
906 //first groupkey is the one we want to modify
907 GroupKey modGroupKey = foundChain.peekFirst();
908 Group modGroup = groupService.getGroup(deviceId, modGroupKey);
909 //second groupkey is the one we wish to remove the reference to
910 GroupKey pointedGroupKey = null;
911 int i = 0;
912 for (GroupKey gk : foundChain) {
913 if (i++ == 1) {
914 pointedGroupKey = gk;
915 break;
916 }
917 }
918 Group pointedGroup = groupService.getGroup(deviceId, pointedGroupKey);
919 GroupBucket bucket = DefaultGroupBucket.createSelectGroupBucket(
920 DefaultTrafficTreatment.builder()
921 .group(pointedGroup.id())
922 .build());
923 GroupBuckets removeBuckets = new GroupBuckets(Collections
924 .singletonList(bucket));
925 log.debug("Removing buckets from group id {} for next id {} in device {}",
926 modGroup.id(), nextObjective.id(), deviceId);
927 groupService.removeBucketsFromGroup(deviceId, modGroupKey,
928 removeBuckets, modGroupKey,
929 nextObjective.appId());
930 //update store
931 allgkeys.remove(index);
932 flowObjectiveStore.putNextGroup(nextObjective.id(),
933 new OfdpaNextGroup(allgkeys, nextObjective));
934 } else {
935 log.warn("Could not find appropriate group-chain for removing bucket"
936 + " for next id {} in dev:{}", nextObjective.id(), deviceId);
937 }
938 }
939
940 /**
941 * Removes all groups in multiple possible group-chains that represent the next
942 * objective.
943 *
944 * @param nextObjective the next objective to remove
945 * @param next the NextGroup that represents the existing group-chain for
946 * this next objective
947 */
948 protected void removeGroup(NextObjective nextObjective, NextGroup next) {
949 List<Deque<GroupKey>> allgkeys = OFDPA2Pipeline.appKryo.deserialize(next.data());
950 allgkeys.forEach(groupChain -> groupChain.forEach(groupKey ->
951 groupService.removeGroup(deviceId, groupKey, nextObjective.appId())));
952 flowObjectiveStore.removeNextGroup(nextObjective.id());
953 }
954
955 /**
956 * Processes next element of a group chain. Assumption is that if this
957 * group points to another group, the latter has already been created
958 * and this driver has received notification for it. A second assumption is
959 * that if there is another group waiting for this group then the appropriate
960 * stores already have the information to act upon the notification for the
961 * creation of this group.
962 * <p>
963 * The processing of the GroupChainElement depends on the number of groups
964 * this element is waiting on. For all group types other than SIMPLE, a
965 * GroupChainElement could be waiting on multiple groups.
966 *
967 * @param gce the group chain element to be processed next
968 */
969 private void processGroupChain(GroupChainElem gce) {
970 int waitOnGroups = gce.decrementAndGetGroupsWaitedOn();
971 if (waitOnGroups != 0) {
972 log.debug("GCE: {} not ready to be processed", gce);
973 return;
974 }
975 log.debug("GCE: {} ready to be processed", gce);
976 if (gce.addBucketToGroup) {
977 groupService.addBucketsToGroup(gce.groupDescription.deviceId(),
978 gce.groupDescription.appCookie(),
979 gce.groupDescription.buckets(),
980 gce.groupDescription.appCookie(),
981 gce.groupDescription.appId());
982 } else {
983 groupService.addGroup(gce.groupDescription);
984 }
985 }
986
987 private class GroupChecker implements Runnable {
988 @Override
989 public void run() {
990 Set<GroupKey> keys = pendingGroups.keySet().stream()
991 .filter(key -> groupService.getGroup(deviceId, key) != null)
992 .collect(Collectors.toSet());
993 Set<GroupKey> otherkeys = pendingNextObjectives.asMap().keySet().stream()
994 .filter(otherkey -> groupService.getGroup(deviceId, otherkey) != null)
995 .collect(Collectors.toSet());
996 keys.addAll(otherkeys);
997
998 keys.stream().forEach(key ->
999 processPendingGroupsOrNextObjectives(key, false));
1000 }
1001 }
1002
1003 private void processPendingGroupsOrNextObjectives(GroupKey key, boolean added) {
1004 //first check for group chain
1005 Set<GroupChainElem> gceSet = pendingGroups.remove(key);
1006 if (gceSet != null) {
1007 for (GroupChainElem gce : gceSet) {
1008 log.info("Group service {} group key {} in device {}. "
1009 + "Processing next group in group chain with group id {}",
1010 (added) ? "ADDED" : "processed",
1011 key, deviceId,
1012 Integer.toHexString(gce.groupDescription.givenGroupId()));
1013 processGroupChain(gce);
1014 }
1015 } else {
1016 // otherwise chain complete - check for waiting nextObjectives
1017 List<OfdpaNextGroup> nextGrpList = pendingNextObjectives.getIfPresent(key);
1018 if (nextGrpList != null) {
1019 pendingNextObjectives.invalidate(key);
1020 nextGrpList.forEach(nextGrp -> {
1021 log.info("Group service {} group key {} in device:{}. "
1022 + "Done implementing next objective: {} <<-->> gid:{}",
1023 (added) ? "ADDED" : "processed",
1024 key, deviceId, nextGrp.nextObjective().id(),
1025 Integer.toHexString(groupService.getGroup(deviceId, key)
1026 .givenGroupId()));
1027 OFDPA2Pipeline.pass(nextGrp.nextObjective());
1028 flowObjectiveStore.putNextGroup(nextGrp.nextObjective().id(), nextGrp);
1029 // check if addBuckets waiting for this completion
1030 NextObjective pendBkt = pendingBuckets
1031 .remove(nextGrp.nextObjective().id());
1032 if (pendBkt != null) {
1033 addBucketToGroup(pendBkt, nextGrp);
1034 }
1035 });
1036 }
1037 }
1038 }
1039
Charles Chane849c192016-01-11 18:28:54 -08001040 private VlanId readVlanFromMeta(NextObjective nextObj) {
1041 TrafficSelector metadata = nextObj.meta();
1042 Criterion criterion = metadata.getCriterion(Criterion.Type.VLAN_VID);
1043 return (criterion == null)
1044 ? null : ((VlanIdCriterion) criterion).vlanId();
1045 }
1046
1047 /**
1048 * Returns a hash as the L2 Interface Group Key.
1049 *
1050 * Keep the lower 6-bit for port since port number usually smaller than 64.
1051 * Hash other information into remaining 28 bits.
1052 *
1053 * @param deviceId Device ID
1054 * @param vlanId VLAN ID
1055 * @param portNumber Port number
1056 * @return L2 interface group key
1057 */
1058 private int l2InterfaceGroupKey(
1059 DeviceId deviceId, VlanId vlanId, long portNumber) {
1060 int portLowerBits = (int) portNumber & PORT_LOWER_BITS_MASK;
1061 long portHigherBits = portNumber & PORT_HIGHER_BITS_MASK;
1062 int hash = Objects.hashCode(deviceId, vlanId, portHigherBits);
1063 return L2_INTERFACE_TYPE | (TYPE_MASK & hash << 6) | portLowerBits;
1064 }
1065
Charles Chan188ebf52015-12-23 00:15:11 -08001066 private class InnerGroupListener implements GroupListener {
1067 @Override
1068 public void event(GroupEvent event) {
1069 log.trace("received group event of type {}", event.type());
1070 if (event.type() == GroupEvent.Type.GROUP_ADDED) {
1071 GroupKey key = event.subject().appCookie();
1072 processPendingGroupsOrNextObjectives(key, true);
1073 }
1074 }
1075 }
1076
1077 /**
1078 * Utility class for moving group information around.
1079 */
1080 private class GroupInfo {
1081 private GroupDescription innerGrpDesc;
1082 private GroupDescription outerGrpDesc;
1083
1084 GroupInfo(GroupDescription innerGrpDesc, GroupDescription outerGrpDesc) {
1085 this.innerGrpDesc = innerGrpDesc;
1086 this.outerGrpDesc = outerGrpDesc;
1087 }
1088 }
1089
1090 /**
1091 * Represents an entire group-chain that implements a Next-Objective from
1092 * the application. The objective is represented as a list of deques, where
1093 * each deque is a separate chain of groups.
1094 * <p>
1095 * For example, an ECMP group with 3 buckets, where each bucket points to
1096 * a group chain of L3 Unicast and L2 interface groups will look like this:
1097 * <ul>
1098 * <li>List[0] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1099 * <li>List[1] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1100 * <li>List[2] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1101 * </ul>
1102 * where the first element of each deque is the same, representing the
1103 * top level ECMP group, while every other element represents a unique groupKey.
1104 * <p>
1105 * Also includes information about the next objective that
1106 * resulted in this group-chain.
1107 *
1108 */
1109 protected class OfdpaNextGroup implements NextGroup {
1110 private final NextObjective nextObj;
1111 private final List<Deque<GroupKey>> gkeys;
1112
1113 public OfdpaNextGroup(List<Deque<GroupKey>> gkeys, NextObjective nextObj) {
1114 this.gkeys = gkeys;
1115 this.nextObj = nextObj;
1116 }
1117
1118 @SuppressWarnings("unused")
1119 public List<Deque<GroupKey>> groupKey() {
1120 return gkeys;
1121 }
1122
1123 public NextObjective nextObjective() {
1124 return nextObj;
1125 }
1126
1127 @Override
1128 public byte[] data() {
1129 return OFDPA2Pipeline.appKryo.serialize(gkeys);
1130 }
1131 }
1132
1133 /**
1134 * Represents a group element that is part of a chain of groups.
1135 * Stores enough information to create a Group Description to add the group
1136 * to the switch by requesting the Group Service. Objects instantiating this
1137 * class are meant to be temporary and live as long as it is needed to wait for
1138 * preceding groups in the group chain to be created.
1139 */
1140 private class GroupChainElem {
1141 private GroupDescription groupDescription;
1142 private AtomicInteger waitOnGroups;
1143 private boolean addBucketToGroup;
1144
1145 GroupChainElem(GroupDescription groupDescription, int waitOnGroups,
1146 boolean addBucketToGroup) {
1147 this.groupDescription = groupDescription;
1148 this.waitOnGroups = new AtomicInteger(waitOnGroups);
1149 this.addBucketToGroup = addBucketToGroup;
1150 }
1151
1152 /**
1153 * This methods atomically decrements the counter for the number of
1154 * groups this GroupChainElement is waiting on, for notifications from
1155 * the Group Service. When this method returns a value of 0, this
1156 * GroupChainElement is ready to be processed.
1157 *
1158 * @return integer indication of the number of notifications being waited on
1159 */
1160 int decrementAndGetGroupsWaitedOn() {
1161 return waitOnGroups.decrementAndGet();
1162 }
1163
1164 @Override
1165 public String toString() {
1166 return (Integer.toHexString(groupDescription.givenGroupId()) +
1167 " groupKey: " + groupDescription.appCookie() +
1168 " waiting-on-groups: " + waitOnGroups.get() +
1169 " addBucketToGroup: " + addBucketToGroup +
1170 " device: " + deviceId);
1171 }
1172 }
1173}