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