blob: 3674aa1cad5e43bd30c8be10ca0cb7ff2ffc48e3 [file] [log] [blame]
Charles Chan188ebf52015-12-23 00:15:11 -08001package org.onosproject.driver.pipeline;
2
3import com.google.common.cache.Cache;
4import com.google.common.cache.CacheBuilder;
5import com.google.common.cache.RemovalCause;
6import com.google.common.cache.RemovalNotification;
7import org.onlab.osgi.ServiceDirectory;
Charles Chan5270ed02016-01-30 23:22:37 -08008import org.onlab.packet.MacAddress;
Charles Chan188ebf52015-12-23 00:15:11 -08009import org.onlab.packet.MplsLabel;
10import org.onlab.packet.VlanId;
11import org.onosproject.core.ApplicationId;
12import org.onosproject.core.DefaultGroupId;
13import org.onosproject.net.DeviceId;
14import org.onosproject.net.PortNumber;
15import org.onosproject.net.behaviour.NextGroup;
16import org.onosproject.net.behaviour.PipelinerContext;
17import org.onosproject.net.flow.DefaultTrafficTreatment;
18import org.onosproject.net.flow.TrafficSelector;
19import org.onosproject.net.flow.TrafficTreatment;
20import org.onosproject.net.flow.criteria.Criterion;
21import org.onosproject.net.flow.criteria.VlanIdCriterion;
22import org.onosproject.net.flow.instructions.Instruction;
23import org.onosproject.net.flow.instructions.Instructions;
24import org.onosproject.net.flow.instructions.L2ModificationInstruction;
25import org.onosproject.net.flowobjective.FlowObjectiveStore;
26import org.onosproject.net.flowobjective.NextObjective;
27import org.onosproject.net.flowobjective.ObjectiveError;
28import org.onosproject.net.group.DefaultGroupBucket;
29import org.onosproject.net.group.DefaultGroupDescription;
30import org.onosproject.net.group.DefaultGroupKey;
31import org.onosproject.net.group.Group;
32import org.onosproject.net.group.GroupBucket;
33import org.onosproject.net.group.GroupBuckets;
34import org.onosproject.net.group.GroupDescription;
35import org.onosproject.net.group.GroupEvent;
36import org.onosproject.net.group.GroupKey;
37import org.onosproject.net.group.GroupListener;
38import org.onosproject.net.group.GroupService;
Saurav Das8be4e3a2016-03-11 17:19:07 -080039import org.onosproject.store.service.AtomicCounter;
40import org.onosproject.store.service.StorageService;
Charles Chan188ebf52015-12-23 00:15:11 -080041import org.slf4j.Logger;
42
43import java.util.ArrayDeque;
44import java.util.ArrayList;
45import java.util.Collection;
46import java.util.Collections;
47import java.util.Deque;
48import java.util.List;
49import java.util.Map;
Charles Chand0fd5dc2016-02-16 23:14:49 -080050import java.util.Objects;
Charles Chan188ebf52015-12-23 00:15:11 -080051import java.util.Set;
52import java.util.concurrent.ConcurrentHashMap;
53import java.util.concurrent.CopyOnWriteArrayList;
54import java.util.concurrent.Executors;
55import java.util.concurrent.ScheduledExecutorService;
56import java.util.concurrent.TimeUnit;
57import java.util.concurrent.atomic.AtomicInteger;
58import java.util.stream.Collectors;
59
60import static org.onlab.util.Tools.groupedThreads;
61import static org.slf4j.LoggerFactory.getLogger;
62
63/**
64 * Group handler for OFDPA2 pipeline.
65 */
66public class OFDPA2GroupHandler {
67 /*
68 * OFDPA requires group-id's to have a certain form.
69 * L2 Interface Groups have <4bits-0><12bits-vlanid><16bits-portid>
70 * L3 Unicast Groups have <4bits-2><28bits-index>
71 * MPLS Interface Groups have <4bits-9><4bits:0><24bits-index>
72 * L3 ECMP Groups have <4bits-7><28bits-index>
73 * L2 Flood Groups have <4bits-4><12bits-vlanid><16bits-index>
74 * L3 VPN Groups have <4bits-9><4bits-2><24bits-index>
75 */
Charles Chane849c192016-01-11 18:28:54 -080076 private static final int L2_INTERFACE_TYPE = 0x00000000;
77 private static final int L3_UNICAST_TYPE = 0x20000000;
78 private static final int MPLS_INTERFACE_TYPE = 0x90000000;
79 private static final int MPLS_L3VPN_SUBTYPE = 0x92000000;
80 private static final int L3_ECMP_TYPE = 0x70000000;
81 private static final int L2_FLOOD_TYPE = 0x40000000;
82
83 private static final int TYPE_MASK = 0x0fffffff;
84 private static final int SUBTYPE_MASK = 0x00ffffff;
85
86 private static final int PORT_LOWER_BITS_MASK = 0x3f;
87 private static final long PORT_HIGHER_BITS_MASK = ~PORT_LOWER_BITS_MASK;
Charles Chan188ebf52015-12-23 00:15:11 -080088
89 private final Logger log = getLogger(getClass());
90 private ServiceDirectory serviceDirectory;
91 protected GroupService groupService;
Saurav Das8be4e3a2016-03-11 17:19:07 -080092 protected StorageService storageService;
Charles Chan188ebf52015-12-23 00:15:11 -080093
94 private DeviceId deviceId;
95 private FlowObjectiveStore flowObjectiveStore;
96 private Cache<GroupKey, List<OfdpaNextGroup>> pendingNextObjectives;
97 private ConcurrentHashMap<GroupKey, Set<GroupChainElem>> pendingGroups;
98 private ScheduledExecutorService groupChecker =
99 Executors.newScheduledThreadPool(2, groupedThreads("onos/pipeliner", "ofdpa2-%d"));
100
101 // index number for group creation
Saurav Das8be4e3a2016-03-11 17:19:07 -0800102 private AtomicCounter nextIndex;
Charles Chan188ebf52015-12-23 00:15:11 -0800103
104 // local stores for port-vlan mapping
105 protected Map<PortNumber, VlanId> port2Vlan = new ConcurrentHashMap<>();
106 protected Map<VlanId, Set<PortNumber>> vlan2Port = new ConcurrentHashMap<>();
107
108 // local store for pending bucketAdds - by design there can only be one
109 // pending bucket for a group
110 protected ConcurrentHashMap<Integer, NextObjective> pendingBuckets = new ConcurrentHashMap<>();
111
112 protected void init(DeviceId deviceId, PipelinerContext context) {
113 this.deviceId = deviceId;
114 this.flowObjectiveStore = context.store();
115 this.serviceDirectory = context.directory();
116 this.groupService = serviceDirectory.get(GroupService.class);
Saurav Das8be4e3a2016-03-11 17:19:07 -0800117 this.storageService = serviceDirectory.get(StorageService.class);
118 this.nextIndex = storageService.atomicCounterBuilder()
119 .withName("group-id-index-counter")
120 .build()
121 .asAtomicCounter();
Charles Chan188ebf52015-12-23 00:15:11 -0800122
123 pendingNextObjectives = CacheBuilder.newBuilder()
124 .expireAfterWrite(20, TimeUnit.SECONDS)
125 .removalListener((
126 RemovalNotification<GroupKey, List<OfdpaNextGroup>> notification) -> {
127 if (notification.getCause() == RemovalCause.EXPIRED) {
128 notification.getValue().forEach(ofdpaNextGrp ->
129 OFDPA2Pipeline.fail(ofdpaNextGrp.nextObj,
130 ObjectiveError.GROUPINSTALLATIONFAILED));
131
132 }
133 }).build();
134 pendingGroups = new ConcurrentHashMap<>();
135 groupChecker.scheduleAtFixedRate(new GroupChecker(), 0, 500, TimeUnit.MILLISECONDS);
136
137 groupService.addListener(new InnerGroupListener());
138 }
139
Saurav Das8be4e3a2016-03-11 17:19:07 -0800140 //////////////////////////////////////
141 // Group Creation
142 //////////////////////////////////////
143
Charles Chan188ebf52015-12-23 00:15:11 -0800144 protected void addGroup(NextObjective nextObjective) {
145 switch (nextObjective.type()) {
146 case SIMPLE:
147 Collection<TrafficTreatment> treatments = nextObjective.next();
148 if (treatments.size() != 1) {
149 log.error("Next Objectives of type Simple should only have a "
150 + "single Traffic Treatment. Next Objective Id:{}",
151 nextObjective.id());
152 OFDPA2Pipeline.fail(nextObjective, ObjectiveError.BADPARAMS);
153 return;
154 }
155 processSimpleNextObjective(nextObjective);
156 break;
157 case BROADCAST:
158 processBroadcastNextObjective(nextObjective);
159 break;
160 case HASHED:
161 processHashedNextObjective(nextObjective);
162 break;
163 case FAILOVER:
164 OFDPA2Pipeline.fail(nextObjective, ObjectiveError.UNSUPPORTED);
165 log.warn("Unsupported next objective type {}", nextObjective.type());
166 break;
167 default:
168 OFDPA2Pipeline.fail(nextObjective, ObjectiveError.UNKNOWN);
169 log.warn("Unknown next objective type {}", nextObjective.type());
170 }
171 }
172
173 /**
174 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
175 * a chain of groups. The simple Next Objective passed
176 * in by the application has to be broken up into a group chain
177 * comprising of an L3 Unicast Group that points to an L2 Interface
178 * Group which in-turn points to an output port. In some cases, the simple
179 * next Objective can just be an L2 interface without the need for chaining.
180 *
181 * @param nextObj the nextObjective of type SIMPLE
182 */
183 private void processSimpleNextObjective(NextObjective nextObj) {
184 TrafficTreatment treatment = nextObj.next().iterator().next();
185 // determine if plain L2 or L3->L2
186 boolean plainL2 = true;
187 for (Instruction ins : treatment.allInstructions()) {
188 if (ins.type() == Instruction.Type.L2MODIFICATION) {
189 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
190 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.ETH_DST ||
191 l2ins.subtype() == L2ModificationInstruction.L2SubType.ETH_SRC) {
192 plainL2 = false;
193 break;
194 }
195 }
196 }
197
198 if (plainL2) {
199 createL2InterfaceGroup(nextObj);
200 return;
201 }
202
203 // break up simple next objective to GroupChain objects
204 GroupInfo groupInfo = createL2L3Chain(treatment, nextObj.id(),
Saurav Das8be4e3a2016-03-11 17:19:07 -0800205 nextObj.appId(), false,
206 nextObj.meta());
Charles Chan188ebf52015-12-23 00:15:11 -0800207 if (groupInfo == null) {
208 log.error("Could not process nextObj={} in dev:{}", nextObj.id(), deviceId);
209 return;
210 }
211 // create object for local and distributed storage
212 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
213 gkeyChain.addFirst(groupInfo.innerGrpDesc.appCookie());
214 gkeyChain.addFirst(groupInfo.outerGrpDesc.appCookie());
215 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(
216 Collections.singletonList(gkeyChain),
217 nextObj);
218
219 // store l3groupkey with the ofdpaNextGroup for the nextObjective that depends on it
220 updatePendingNextObjective(groupInfo.outerGrpDesc.appCookie(), ofdpaGrp);
221
222 // now we are ready to send the l2 groupDescription (inner), as all the stores
223 // that will get async replies have been updated. By waiting to update
224 // the stores, we prevent nasty race conditions.
225 groupService.addGroup(groupInfo.innerGrpDesc);
226 }
227
Charles Chan188ebf52015-12-23 00:15:11 -0800228 /**
229 * Creates a simple L2 Interface Group.
230 *
231 * @param nextObj the next Objective
232 */
233 private void createL2InterfaceGroup(NextObjective nextObj) {
234 // only allowed actions are vlan pop and outport
235 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
236 PortNumber portNum = null;
237 for (Instruction ins : nextObj.next().iterator().next().allInstructions()) {
238 if (ins.type() == Instruction.Type.L2MODIFICATION) {
239 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
240 switch (l2ins.subtype()) {
241 case VLAN_POP:
242 ttb.add(l2ins);
243 break;
244 default:
245 break;
246 }
247 } else if (ins.type() == Instruction.Type.OUTPUT) {
248 portNum = ((Instructions.OutputInstruction) ins).port();
249 ttb.add(ins);
250 } else {
251 log.warn("Driver does not handle this type of TrafficTreatment"
252 + " instruction in simple nextObjectives: {}", ins.type());
253 }
254 }
Charles Chan188ebf52015-12-23 00:15:11 -0800255
Charles Chane849c192016-01-11 18:28:54 -0800256 VlanId vlanId = readVlanFromMeta(nextObj);
257 if (vlanId == null) {
Charles Chan188ebf52015-12-23 00:15:11 -0800258 log.error("Driver cannot process an L2/L3 group chain without "
259 + "egress vlan information for dev: {} port:{}",
260 deviceId, portNum);
261 return;
262 }
263
264 // assemble information for ofdpa l2interface group
Charles Chane849c192016-01-11 18:28:54 -0800265 int l2groupId = L2_INTERFACE_TYPE | (vlanId.toShort() << 16) | (int) portNum.toLong();
Charles Chan188ebf52015-12-23 00:15:11 -0800266 // a globally unique groupkey that is different for ports in the same devices
267 // but different for the same portnumber on different devices. Also different
268 // for the various group-types created out of the same next objective.
Charles Chane849c192016-01-11 18:28:54 -0800269 int l2gk = l2InterfaceGroupKey(deviceId, vlanId, portNum.toLong());
Charles Chan188ebf52015-12-23 00:15:11 -0800270 final GroupKey l2groupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l2gk));
271
272 // create group description for the l2interfacegroup
273 GroupBucket l2interfaceGroupBucket =
274 DefaultGroupBucket.createIndirectGroupBucket(ttb.build());
275 GroupDescription l2groupDescription =
276 new DefaultGroupDescription(
277 deviceId,
278 GroupDescription.Type.INDIRECT,
279 new GroupBuckets(Collections.singletonList(
280 l2interfaceGroupBucket)),
281 l2groupkey,
282 l2groupId,
283 nextObj.appId());
284 log.debug("Trying L2Interface: device:{} gid:{} gkey:{} nextId:{}",
285 deviceId, Integer.toHexString(l2groupId),
286 l2groupkey, nextObj.id());
287
288 // create object for local and distributed storage
289 Deque<GroupKey> singleKey = new ArrayDeque<>();
290 singleKey.addFirst(l2groupkey);
291 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(
292 Collections.singletonList(singleKey),
293 nextObj);
294
295 // store l2groupkey for the nextObjective that depends on it
296 updatePendingNextObjective(l2groupkey, ofdpaGrp);
297 // send the group description to the group service
298 groupService.addGroup(l2groupDescription);
299 }
300
301 /**
302 * Creates one of two possible group-chains from the treatment
303 * passed in. Depending on the MPLS boolean, this method either creates
304 * an L3Unicast Group --> L2Interface Group, if mpls is false;
305 * or MPLSInterface Group --> L2Interface Group, if mpls is true;
306 * The returned 'inner' group description is always the L2 Interface group.
307 *
308 * @param treatment that needs to be broken up to create the group chain
309 * @param nextId of the next objective that needs this group chain
310 * @param appId of the application that sent this next objective
311 * @param mpls determines if L3Unicast or MPLSInterface group is created
312 * @param meta metadata passed in by the application as part of the nextObjective
313 * @return GroupInfo containing the GroupDescription of the
314 * L2Interface group(inner) and the GroupDescription of the (outer)
315 * L3Unicast/MPLSInterface group. May return null if there is an
316 * error in processing the chain
317 */
318 private GroupInfo createL2L3Chain(TrafficTreatment treatment, int nextId,
Saurav Das8be4e3a2016-03-11 17:19:07 -0800319 ApplicationId appId, boolean mpls,
320 TrafficSelector meta) {
Charles Chan188ebf52015-12-23 00:15:11 -0800321 // for the l2interface group, get vlan and port info
322 // for the outer group, get the src/dst mac, and vlan info
323 TrafficTreatment.Builder outerTtb = DefaultTrafficTreatment.builder();
324 TrafficTreatment.Builder innerTtb = DefaultTrafficTreatment.builder();
325 VlanId vlanid = null;
326 long portNum = 0;
327 boolean setVlan = false, popVlan = false;
Charles Chand0fd5dc2016-02-16 23:14:49 -0800328 MacAddress srcMac = MacAddress.ZERO;
Charles Chan5270ed02016-01-30 23:22:37 -0800329 MacAddress dstMac = MacAddress.ZERO;
Charles Chan188ebf52015-12-23 00:15:11 -0800330 for (Instruction ins : treatment.allInstructions()) {
331 if (ins.type() == Instruction.Type.L2MODIFICATION) {
332 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
333 switch (l2ins.subtype()) {
334 case ETH_DST:
Charles Chan5270ed02016-01-30 23:22:37 -0800335 dstMac = ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac();
336 outerTtb.setEthDst(dstMac);
Charles Chan188ebf52015-12-23 00:15:11 -0800337 break;
338 case ETH_SRC:
Charles Chand0fd5dc2016-02-16 23:14:49 -0800339 srcMac = ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac();
340 outerTtb.setEthSrc(srcMac);
Charles Chan188ebf52015-12-23 00:15:11 -0800341 break;
342 case VLAN_ID:
343 vlanid = ((L2ModificationInstruction.ModVlanIdInstruction) l2ins).vlanId();
344 outerTtb.setVlanId(vlanid);
345 setVlan = true;
346 break;
347 case VLAN_POP:
348 innerTtb.popVlan();
349 popVlan = true;
350 break;
351 case DEC_MPLS_TTL:
352 case MPLS_LABEL:
353 case MPLS_POP:
354 case MPLS_PUSH:
355 case VLAN_PCP:
356 case VLAN_PUSH:
357 default:
358 break;
359 }
360 } else if (ins.type() == Instruction.Type.OUTPUT) {
361 portNum = ((Instructions.OutputInstruction) ins).port().toLong();
362 innerTtb.add(ins);
363 } else {
364 log.warn("Driver does not handle this type of TrafficTreatment"
365 + " instruction in nextObjectives: {}", ins.type());
366 }
367 }
368
369 if (vlanid == null && meta != null) {
370 // use metadata if available
371 Criterion vidCriterion = meta.getCriterion(Criterion.Type.VLAN_VID);
372 if (vidCriterion != null) {
373 vlanid = ((VlanIdCriterion) vidCriterion).vlanId();
374 }
375 // if vlan is not set, use the vlan in metadata for outerTtb
376 if (vlanid != null && !setVlan) {
377 outerTtb.setVlanId(vlanid);
378 }
379 }
380
381 if (vlanid == null) {
382 log.error("Driver cannot process an L2/L3 group chain without "
383 + "egress vlan information for dev: {} port:{}",
384 deviceId, portNum);
385 return null;
386 }
387
388 if (!setVlan && !popVlan) {
389 // untagged outgoing port
390 TrafficTreatment.Builder temp = DefaultTrafficTreatment.builder();
391 temp.popVlan();
392 innerTtb.build().allInstructions().forEach(i -> temp.add(i));
393 innerTtb = temp;
394 }
395
396 // assemble information for ofdpa l2interface group
Charles Chane849c192016-01-11 18:28:54 -0800397 int l2groupId = L2_INTERFACE_TYPE | (vlanid.toShort() << 16) | (int) portNum;
Saurav Das8be4e3a2016-03-11 17:19:07 -0800398 // a globally unique groupkey that is different for ports in the same device,
Charles Chan188ebf52015-12-23 00:15:11 -0800399 // but different for the same portnumber on different devices. Also different
400 // for the various group-types created out of the same next objective.
Charles Chane849c192016-01-11 18:28:54 -0800401 int l2gk = l2InterfaceGroupKey(deviceId, vlanid, portNum);
Charles Chan188ebf52015-12-23 00:15:11 -0800402 final GroupKey l2groupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l2gk));
403
404 // assemble information for outer group
405 GroupDescription outerGrpDesc = null;
406 if (mpls) {
407 // outer group is MPLSInteface
Saurav Das8be4e3a2016-03-11 17:19:07 -0800408 int mplsInterfaceIndex = getNextAvailableIndex();
409 int mplsgroupId = MPLS_INTERFACE_TYPE | (SUBTYPE_MASK & mplsInterfaceIndex);
410 final GroupKey mplsgroupkey = new DefaultGroupKey(
411 OFDPA2Pipeline.appKryo.serialize(mplsInterfaceIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800412 outerTtb.group(new DefaultGroupId(l2groupId));
413 // create the mpls-interface group description to wait for the
414 // l2 interface group to be processed
415 GroupBucket mplsinterfaceGroupBucket =
416 DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
417 outerGrpDesc = new DefaultGroupDescription(
418 deviceId,
419 GroupDescription.Type.INDIRECT,
420 new GroupBuckets(Collections.singletonList(
421 mplsinterfaceGroupBucket)),
422 mplsgroupkey,
423 mplsgroupId,
424 appId);
425 log.debug("Trying MPLS-Interface: device:{} gid:{} gkey:{} nextid:{}",
426 deviceId, Integer.toHexString(mplsgroupId),
427 mplsgroupkey, nextId);
428 } else {
429 // outer group is L3Unicast
Saurav Das8be4e3a2016-03-11 17:19:07 -0800430 int l3unicastIndex = getNextAvailableIndex();
431 int l3groupId = L3_UNICAST_TYPE | (TYPE_MASK & l3unicastIndex);
432 final GroupKey l3groupkey = new DefaultGroupKey(
433 OFDPA2Pipeline.appKryo.serialize(l3unicastIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800434 outerTtb.group(new DefaultGroupId(l2groupId));
435 // create the l3unicast group description to wait for the
436 // l2 interface group to be processed
437 GroupBucket l3unicastGroupBucket =
438 DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
439 outerGrpDesc = new DefaultGroupDescription(
440 deviceId,
441 GroupDescription.Type.INDIRECT,
442 new GroupBuckets(Collections.singletonList(
443 l3unicastGroupBucket)),
444 l3groupkey,
445 l3groupId,
446 appId);
447 log.debug("Trying L3Unicast: device:{} gid:{} gkey:{} nextid:{}",
448 deviceId, Integer.toHexString(l3groupId),
449 l3groupkey, nextId);
450 }
451
452 // store l2groupkey with the groupChainElem for the outer-group that depends on it
453 GroupChainElem gce = new GroupChainElem(outerGrpDesc, 1, false);
454 updatePendingGroups(l2groupkey, gce);
455
456 // create group description for the inner l2interfacegroup
457 GroupBucket l2interfaceGroupBucket =
458 DefaultGroupBucket.createIndirectGroupBucket(innerTtb.build());
459 GroupDescription l2groupDescription =
460 new DefaultGroupDescription(
461 deviceId,
462 GroupDescription.Type.INDIRECT,
463 new GroupBuckets(Collections.singletonList(
464 l2interfaceGroupBucket)),
465 l2groupkey,
466 l2groupId,
467 appId);
468 log.debug("Trying L2Interface: device:{} gid:{} gkey:{} nextId:{}",
469 deviceId, Integer.toHexString(l2groupId),
470 l2groupkey, nextId);
471 return new GroupInfo(l2groupDescription, outerGrpDesc);
472
473 }
474
475 /**
476 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
477 * a chain of groups. The broadcast Next Objective passed in by the application
478 * has to be broken up into a group chain comprising of an
479 * L2 Flood group whose buckets point to L2 Interface groups.
480 *
481 * @param nextObj the nextObjective of type BROADCAST
482 */
483 private void processBroadcastNextObjective(NextObjective nextObj) {
484 // break up broadcast next objective to multiple groups
485 Collection<TrafficTreatment> buckets = nextObj.next();
486
Charles Chane849c192016-01-11 18:28:54 -0800487 VlanId vlanId = readVlanFromMeta(nextObj);
488 if (vlanId == null) {
Charles Chan188ebf52015-12-23 00:15:11 -0800489 log.warn("Required VLAN ID info in nextObj metadata but not found. Aborting");
490 return;
491 }
Charles Chan188ebf52015-12-23 00:15:11 -0800492
493 // each treatment is converted to an L2 interface group
494 List<GroupDescription> l2interfaceGroupDescs = new ArrayList<>();
495 List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
496 for (TrafficTreatment treatment : buckets) {
497 TrafficTreatment.Builder newTreatment = DefaultTrafficTreatment.builder();
498 PortNumber portNum = null;
499 // ensure that the only allowed treatments are pop-vlan and output
500 for (Instruction ins : treatment.allInstructions()) {
501 if (ins.type() == Instruction.Type.L2MODIFICATION) {
502 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
503 switch (l2ins.subtype()) {
504 case VLAN_POP:
505 newTreatment.add(l2ins);
506 break;
507 default:
508 log.debug("action {} not permitted for broadcast nextObj",
509 l2ins.subtype());
510 break;
511 }
512 } else if (ins.type() == Instruction.Type.OUTPUT) {
513 portNum = ((Instructions.OutputInstruction) ins).port();
514 newTreatment.add(ins);
515 } else {
Charles Chane849c192016-01-11 18:28:54 -0800516 log.debug("TrafficTreatment of type {} not permitted in " +
517 " broadcast nextObjective", ins.type());
Charles Chan188ebf52015-12-23 00:15:11 -0800518 }
519 }
520
Charles Chan188ebf52015-12-23 00:15:11 -0800521 // assemble info for l2 interface group
Charles Chane849c192016-01-11 18:28:54 -0800522 int l2gk = l2InterfaceGroupKey(deviceId, vlanId, portNum.toLong());
Charles Chan188ebf52015-12-23 00:15:11 -0800523 final GroupKey l2groupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l2gk));
Charles Chane849c192016-01-11 18:28:54 -0800524 int l2groupId = L2_INTERFACE_TYPE | (vlanId.toShort() << 16) |
Charles Chan188ebf52015-12-23 00:15:11 -0800525 (int) portNum.toLong();
526 GroupBucket l2interfaceGroupBucket =
527 DefaultGroupBucket.createIndirectGroupBucket(newTreatment.build());
528 GroupDescription l2interfaceGroupDescription =
529 new DefaultGroupDescription(
530 deviceId,
531 GroupDescription.Type.INDIRECT,
532 new GroupBuckets(Collections.singletonList(
533 l2interfaceGroupBucket)),
534 l2groupkey,
535 l2groupId,
536 nextObj.appId());
537 log.debug("Trying L2-Interface: device:{} gid:{} gkey:{} nextid:{}",
538 deviceId, Integer.toHexString(l2groupId),
539 l2groupkey, nextObj.id());
540
541 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
542 gkeyChain.addFirst(l2groupkey);
543
544 // store the info needed to create this group
545 l2interfaceGroupDescs.add(l2interfaceGroupDescription);
546 allGroupKeys.add(gkeyChain);
547 }
548
549 // assemble info for l2 flood group
Saurav Das0fd79d92016-03-07 10:58:36 -0800550 // since there can be only one flood group for a vlan, its index is always the same - 0
551 Integer l2floodgroupId = L2_FLOOD_TYPE | (vlanId.toShort() << 16);
Saurav Das8be4e3a2016-03-11 17:19:07 -0800552 int l2floodgk = getNextAvailableIndex();
Charles Chan188ebf52015-12-23 00:15:11 -0800553 final GroupKey l2floodgroupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l2floodgk));
554 // collection of group buckets pointing to all the l2 interface groups
555 List<GroupBucket> l2floodBuckets = new ArrayList<>();
556 for (GroupDescription l2intGrpDesc : l2interfaceGroupDescs) {
557 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
558 ttb.group(new DefaultGroupId(l2intGrpDesc.givenGroupId()));
559 GroupBucket abucket = DefaultGroupBucket.createAllGroupBucket(ttb.build());
560 l2floodBuckets.add(abucket);
561 }
562 // create the l2flood group-description to wait for all the
563 // l2interface groups to be processed
564 GroupDescription l2floodGroupDescription =
565 new DefaultGroupDescription(
566 deviceId,
567 GroupDescription.Type.ALL,
568 new GroupBuckets(l2floodBuckets),
569 l2floodgroupkey,
570 l2floodgroupId,
571 nextObj.appId());
572 GroupChainElem gce = new GroupChainElem(l2floodGroupDescription,
573 l2interfaceGroupDescs.size(),
574 false);
575 log.debug("Trying L2-Flood: device:{} gid:{} gkey:{} nextid:{}",
576 deviceId, Integer.toHexString(l2floodgroupId),
577 l2floodgroupkey, nextObj.id());
578
579 // create objects for local and distributed storage
580 allGroupKeys.forEach(gkeyChain -> gkeyChain.addFirst(l2floodgroupkey));
581 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
582
583 // store l2floodgroupkey with the ofdpaGroupChain for the nextObjective
584 // that depends on it
585 updatePendingNextObjective(l2floodgroupkey, ofdpaGrp);
586
587 for (GroupDescription l2intGrpDesc : l2interfaceGroupDescs) {
588 // store all l2groupkeys with the groupChainElem for the l2floodgroup
589 // that depends on it
590 updatePendingGroups(l2intGrpDesc.appCookie(), gce);
591 // send groups for all l2 interface groups
592 groupService.addGroup(l2intGrpDesc);
593 }
594 }
595
Charles Chan188ebf52015-12-23 00:15:11 -0800596 /**
597 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
598 * a chain of groups. The hashed Next Objective passed in by the application
599 * has to be broken up into a group chain comprising of an
600 * L3 ECMP group as the top level group. Buckets of this group can point
601 * to a variety of groups in a group chain, depending on the whether
602 * MPLS labels are being pushed or not.
603 * <p>
604 * NOTE: We do not create MPLS ECMP groups as they are unimplemented in
605 * OF-DPA 2.0 (even though it is in the spec). Therefore we do not
606 * check the nextObjective meta to see what is matching before being
607 * sent to this nextObjective.
608 *
609 * @param nextObj the nextObjective of type HASHED
610 */
611 private void processHashedNextObjective(NextObjective nextObj) {
612 // storage for all group keys in the chain of groups created
613 List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
614 List<GroupInfo> unsentGroups = new ArrayList<>();
615 createHashBucketChains(nextObj, allGroupKeys, unsentGroups);
616
617 // now we can create the outermost L3 ECMP group
618 List<GroupBucket> l3ecmpGroupBuckets = new ArrayList<>();
619 for (GroupInfo gi : unsentGroups) {
620 // create ECMP bucket to point to the outer group
621 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
622 ttb.group(new DefaultGroupId(gi.outerGrpDesc.givenGroupId()));
623 GroupBucket sbucket = DefaultGroupBucket
624 .createSelectGroupBucket(ttb.build());
625 l3ecmpGroupBuckets.add(sbucket);
626 }
Saurav Das8be4e3a2016-03-11 17:19:07 -0800627 int l3ecmpIndex = getNextAvailableIndex();
628 int l3ecmpGroupId = L3_ECMP_TYPE | (TYPE_MASK & l3ecmpIndex);
629 GroupKey l3ecmpGroupKey = new DefaultGroupKey(
630 OFDPA2Pipeline.appKryo.serialize(l3ecmpIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800631 GroupDescription l3ecmpGroupDesc =
632 new DefaultGroupDescription(
633 deviceId,
634 GroupDescription.Type.SELECT,
635 new GroupBuckets(l3ecmpGroupBuckets),
636 l3ecmpGroupKey,
637 l3ecmpGroupId,
638 nextObj.appId());
639 GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc,
640 l3ecmpGroupBuckets.size(),
641 false);
642
643 // create objects for local and distributed storage
644 allGroupKeys.forEach(gkeyChain -> gkeyChain.addFirst(l3ecmpGroupKey));
645 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
646
647 // store l3ecmpGroupKey with the ofdpaGroupChain for the nextObjective
648 // that depends on it
649 updatePendingNextObjective(l3ecmpGroupKey, ofdpaGrp);
650
651 log.debug("Trying L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
652 deviceId, Integer.toHexString(l3ecmpGroupId),
653 l3ecmpGroupKey, nextObj.id());
654 // finally we are ready to send the innermost groups
655 for (GroupInfo gi : unsentGroups) {
656 log.debug("Sending innermost group {} in group chain on device {} ",
657 Integer.toHexString(gi.innerGrpDesc.givenGroupId()), deviceId);
658 updatePendingGroups(gi.outerGrpDesc.appCookie(), l3ecmpGce);
659 groupService.addGroup(gi.innerGrpDesc);
660 }
661
662 }
663
664 /**
665 * Creates group chains for all buckets in a hashed group, and stores the
666 * GroupInfos and GroupKeys for all the groups in the lists passed in, which
667 * should be empty.
668 * <p>
669 * Does not create the top level ECMP group. Does not actually send the
670 * groups to the groupService.
671 *
672 * @param nextObj the Next Objective with buckets that need to be converted
673 * to group chains
674 * @param allGroupKeys a list to store groupKey for each bucket-group-chain
675 * @param unsentGroups a list to store GroupInfo for each bucket-group-chain
676 */
677 private void createHashBucketChains(NextObjective nextObj,
Saurav Das8be4e3a2016-03-11 17:19:07 -0800678 List<Deque<GroupKey>> allGroupKeys,
679 List<GroupInfo> unsentGroups) {
Charles Chan188ebf52015-12-23 00:15:11 -0800680 // break up hashed next objective to multiple groups
681 Collection<TrafficTreatment> buckets = nextObj.next();
682
683 for (TrafficTreatment bucket : buckets) {
684 //figure out how many labels are pushed in each bucket
685 int labelsPushed = 0;
686 MplsLabel innermostLabel = null;
687 for (Instruction ins : bucket.allInstructions()) {
688 if (ins.type() == Instruction.Type.L2MODIFICATION) {
689 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
690 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.MPLS_PUSH) {
691 labelsPushed++;
692 }
693 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.MPLS_LABEL) {
694 if (innermostLabel == null) {
Ray Milkey125572b2016-02-22 16:48:17 -0800695 innermostLabel = ((L2ModificationInstruction.ModMplsLabelInstruction) l2ins).label();
Charles Chan188ebf52015-12-23 00:15:11 -0800696 }
697 }
698 }
699 }
700
701 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
702 // XXX we only deal with 0 and 1 label push right now
703 if (labelsPushed == 0) {
704 GroupInfo nolabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
705 nextObj.appId(), false,
706 nextObj.meta());
707 if (nolabelGroupInfo == null) {
708 log.error("Could not process nextObj={} in dev:{}",
709 nextObj.id(), deviceId);
710 return;
711 }
712 gkeyChain.addFirst(nolabelGroupInfo.innerGrpDesc.appCookie());
713 gkeyChain.addFirst(nolabelGroupInfo.outerGrpDesc.appCookie());
714
715 // we can't send the inner group description yet, as we have to
716 // create the dependent ECMP group first. So we store..
717 unsentGroups.add(nolabelGroupInfo);
718
719 } else if (labelsPushed == 1) {
720 GroupInfo onelabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
721 nextObj.appId(), true,
722 nextObj.meta());
723 if (onelabelGroupInfo == null) {
724 log.error("Could not process nextObj={} in dev:{}",
725 nextObj.id(), deviceId);
726 return;
727 }
728 // we need to add another group to this chain - the L3VPN group
729 TrafficTreatment.Builder l3vpnTtb = DefaultTrafficTreatment.builder();
730 l3vpnTtb.pushMpls()
731 .setMpls(innermostLabel)
732 .setMplsBos(true)
733 .copyTtlOut()
734 .group(new DefaultGroupId(
735 onelabelGroupInfo.outerGrpDesc.givenGroupId()));
736 GroupBucket l3vpnGrpBkt =
737 DefaultGroupBucket.createIndirectGroupBucket(l3vpnTtb.build());
Saurav Das8be4e3a2016-03-11 17:19:07 -0800738 int l3vpnIndex = getNextAvailableIndex();
739 int l3vpngroupId = MPLS_L3VPN_SUBTYPE | (SUBTYPE_MASK & l3vpnIndex);
740 GroupKey l3vpngroupkey = new DefaultGroupKey(
741 OFDPA2Pipeline.appKryo.serialize(l3vpnIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800742 GroupDescription l3vpnGroupDesc =
743 new DefaultGroupDescription(
744 deviceId,
745 GroupDescription.Type.INDIRECT,
746 new GroupBuckets(Collections.singletonList(
747 l3vpnGrpBkt)),
748 l3vpngroupkey,
749 l3vpngroupId,
750 nextObj.appId());
751 GroupChainElem l3vpnGce = new GroupChainElem(l3vpnGroupDesc, 1, false);
752 updatePendingGroups(onelabelGroupInfo.outerGrpDesc.appCookie(), l3vpnGce);
753
754 gkeyChain.addFirst(onelabelGroupInfo.innerGrpDesc.appCookie());
755 gkeyChain.addFirst(onelabelGroupInfo.outerGrpDesc.appCookie());
756 gkeyChain.addFirst(l3vpngroupkey);
757
758 //now we can replace the outerGrpDesc with the one we just created
759 onelabelGroupInfo.outerGrpDesc = l3vpnGroupDesc;
760
761 // we can't send the innermost group yet, as we have to create
762 // the dependent ECMP group first. So we store ...
763 unsentGroups.add(onelabelGroupInfo);
764
765 log.debug("Trying L3VPN: device:{} gid:{} gkey:{} nextId:{}",
766 deviceId, Integer.toHexString(l3vpngroupId),
767 l3vpngroupkey, nextObj.id());
768
769 } else {
770 log.warn("Driver currently does not handle more than 1 MPLS "
771 + "labels. Not processing nextObjective {}", nextObj.id());
772 return;
773 }
774
775 // all groups in this chain
776 allGroupKeys.add(gkeyChain);
777 }
778 }
779
Saurav Das8be4e3a2016-03-11 17:19:07 -0800780 //////////////////////////////////////
781 // Group Editing
782 //////////////////////////////////////
783
Charles Chan188ebf52015-12-23 00:15:11 -0800784 /**
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
Saurav Das8be4e3a2016-03-11 17:19:07 -0800955 //////////////////////////////////////
956 // Helper Methods and Classes
957 //////////////////////////////////////
958
959 private void updatePendingNextObjective(GroupKey key, OfdpaNextGroup value) {
960 List<OfdpaNextGroup> nextList = new CopyOnWriteArrayList<OfdpaNextGroup>();
961 nextList.add(value);
962 List<OfdpaNextGroup> ret = pendingNextObjectives.asMap()
963 .putIfAbsent(key, nextList);
964 if (ret != null) {
965 ret.add(value);
966 }
967 }
968
969 private void updatePendingGroups(GroupKey gkey, GroupChainElem gce) {
970 Set<GroupChainElem> gceSet = Collections.newSetFromMap(
971 new ConcurrentHashMap<GroupChainElem, Boolean>());
972 gceSet.add(gce);
973 Set<GroupChainElem> retval = pendingGroups.putIfAbsent(gkey, gceSet);
974 if (retval != null) {
975 retval.add(gce);
976 }
977 }
978
Charles Chan188ebf52015-12-23 00:15:11 -0800979 /**
980 * Processes next element of a group chain. Assumption is that if this
981 * group points to another group, the latter has already been created
982 * and this driver has received notification for it. A second assumption is
983 * that if there is another group waiting for this group then the appropriate
984 * stores already have the information to act upon the notification for the
985 * creation of this group.
986 * <p>
987 * The processing of the GroupChainElement depends on the number of groups
988 * this element is waiting on. For all group types other than SIMPLE, a
989 * GroupChainElement could be waiting on multiple groups.
990 *
991 * @param gce the group chain element to be processed next
992 */
993 private void processGroupChain(GroupChainElem gce) {
994 int waitOnGroups = gce.decrementAndGetGroupsWaitedOn();
995 if (waitOnGroups != 0) {
996 log.debug("GCE: {} not ready to be processed", gce);
997 return;
998 }
999 log.debug("GCE: {} ready to be processed", gce);
1000 if (gce.addBucketToGroup) {
1001 groupService.addBucketsToGroup(gce.groupDescription.deviceId(),
1002 gce.groupDescription.appCookie(),
1003 gce.groupDescription.buckets(),
1004 gce.groupDescription.appCookie(),
1005 gce.groupDescription.appId());
1006 } else {
1007 groupService.addGroup(gce.groupDescription);
1008 }
1009 }
1010
1011 private class GroupChecker implements Runnable {
1012 @Override
1013 public void run() {
1014 Set<GroupKey> keys = pendingGroups.keySet().stream()
1015 .filter(key -> groupService.getGroup(deviceId, key) != null)
1016 .collect(Collectors.toSet());
1017 Set<GroupKey> otherkeys = pendingNextObjectives.asMap().keySet().stream()
1018 .filter(otherkey -> groupService.getGroup(deviceId, otherkey) != null)
1019 .collect(Collectors.toSet());
1020 keys.addAll(otherkeys);
1021
1022 keys.stream().forEach(key ->
1023 processPendingGroupsOrNextObjectives(key, false));
1024 }
1025 }
1026
Saurav Das8be4e3a2016-03-11 17:19:07 -08001027 private class InnerGroupListener implements GroupListener {
1028 @Override
1029 public void event(GroupEvent event) {
1030 log.trace("received group event of type {}", event.type());
1031 if (event.type() == GroupEvent.Type.GROUP_ADDED) {
1032 GroupKey key = event.subject().appCookie();
1033 processPendingGroupsOrNextObjectives(key, true);
1034 }
1035 }
1036 }
1037
Charles Chan188ebf52015-12-23 00:15:11 -08001038 private void processPendingGroupsOrNextObjectives(GroupKey key, boolean added) {
1039 //first check for group chain
1040 Set<GroupChainElem> gceSet = pendingGroups.remove(key);
1041 if (gceSet != null) {
1042 for (GroupChainElem gce : gceSet) {
1043 log.info("Group service {} group key {} in device {}. "
Saurav Das0fd79d92016-03-07 10:58:36 -08001044 + "Processing next group in group chain with group id 0x{}",
Charles Chan188ebf52015-12-23 00:15:11 -08001045 (added) ? "ADDED" : "processed",
1046 key, deviceId,
1047 Integer.toHexString(gce.groupDescription.givenGroupId()));
1048 processGroupChain(gce);
1049 }
1050 } else {
1051 // otherwise chain complete - check for waiting nextObjectives
1052 List<OfdpaNextGroup> nextGrpList = pendingNextObjectives.getIfPresent(key);
1053 if (nextGrpList != null) {
1054 pendingNextObjectives.invalidate(key);
1055 nextGrpList.forEach(nextGrp -> {
1056 log.info("Group service {} group key {} in device:{}. "
Saurav Das0fd79d92016-03-07 10:58:36 -08001057 + "Done implementing next objective: {} <<-->> gid:0x{}",
Charles Chan188ebf52015-12-23 00:15:11 -08001058 (added) ? "ADDED" : "processed",
1059 key, deviceId, nextGrp.nextObjective().id(),
1060 Integer.toHexString(groupService.getGroup(deviceId, key)
1061 .givenGroupId()));
1062 OFDPA2Pipeline.pass(nextGrp.nextObjective());
1063 flowObjectiveStore.putNextGroup(nextGrp.nextObjective().id(), nextGrp);
1064 // check if addBuckets waiting for this completion
1065 NextObjective pendBkt = pendingBuckets
1066 .remove(nextGrp.nextObjective().id());
1067 if (pendBkt != null) {
1068 addBucketToGroup(pendBkt, nextGrp);
1069 }
1070 });
1071 }
1072 }
1073 }
1074
Charles Chane849c192016-01-11 18:28:54 -08001075 private VlanId readVlanFromMeta(NextObjective nextObj) {
1076 TrafficSelector metadata = nextObj.meta();
1077 Criterion criterion = metadata.getCriterion(Criterion.Type.VLAN_VID);
1078 return (criterion == null)
1079 ? null : ((VlanIdCriterion) criterion).vlanId();
1080 }
1081
Saurav Das8be4e3a2016-03-11 17:19:07 -08001082 private int getNextAvailableIndex() {
1083 return (int) nextIndex.incrementAndGet();
1084 }
1085
Charles Chane849c192016-01-11 18:28:54 -08001086 /**
1087 * Returns a hash as the L2 Interface Group Key.
1088 *
1089 * Keep the lower 6-bit for port since port number usually smaller than 64.
1090 * Hash other information into remaining 28 bits.
1091 *
1092 * @param deviceId Device ID
1093 * @param vlanId VLAN ID
1094 * @param portNumber Port number
1095 * @return L2 interface group key
1096 */
1097 private int l2InterfaceGroupKey(
1098 DeviceId deviceId, VlanId vlanId, long portNumber) {
1099 int portLowerBits = (int) portNumber & PORT_LOWER_BITS_MASK;
1100 long portHigherBits = portNumber & PORT_HIGHER_BITS_MASK;
Charles Chand0fd5dc2016-02-16 23:14:49 -08001101 int hash = Objects.hash(deviceId, vlanId, portHigherBits);
Charles Chane849c192016-01-11 18:28:54 -08001102 return L2_INTERFACE_TYPE | (TYPE_MASK & hash << 6) | portLowerBits;
1103 }
1104
Charles Chan188ebf52015-12-23 00:15:11 -08001105 /**
1106 * Utility class for moving group information around.
1107 */
1108 private class GroupInfo {
1109 private GroupDescription innerGrpDesc;
1110 private GroupDescription outerGrpDesc;
1111
1112 GroupInfo(GroupDescription innerGrpDesc, GroupDescription outerGrpDesc) {
1113 this.innerGrpDesc = innerGrpDesc;
1114 this.outerGrpDesc = outerGrpDesc;
1115 }
1116 }
1117
1118 /**
1119 * Represents an entire group-chain that implements a Next-Objective from
1120 * the application. The objective is represented as a list of deques, where
1121 * each deque is a separate chain of groups.
1122 * <p>
1123 * For example, an ECMP group with 3 buckets, where each bucket points to
1124 * a group chain of L3 Unicast and L2 interface groups will look like this:
1125 * <ul>
1126 * <li>List[0] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1127 * <li>List[1] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1128 * <li>List[2] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1129 * </ul>
1130 * where the first element of each deque is the same, representing the
1131 * top level ECMP group, while every other element represents a unique groupKey.
1132 * <p>
1133 * Also includes information about the next objective that
1134 * resulted in this group-chain.
1135 *
1136 */
1137 protected class OfdpaNextGroup implements NextGroup {
1138 private final NextObjective nextObj;
1139 private final List<Deque<GroupKey>> gkeys;
1140
1141 public OfdpaNextGroup(List<Deque<GroupKey>> gkeys, NextObjective nextObj) {
1142 this.gkeys = gkeys;
1143 this.nextObj = nextObj;
1144 }
1145
Charles Chan188ebf52015-12-23 00:15:11 -08001146 public List<Deque<GroupKey>> groupKey() {
1147 return gkeys;
1148 }
1149
1150 public NextObjective nextObjective() {
1151 return nextObj;
1152 }
1153
1154 @Override
1155 public byte[] data() {
1156 return OFDPA2Pipeline.appKryo.serialize(gkeys);
1157 }
1158 }
1159
1160 /**
1161 * Represents a group element that is part of a chain of groups.
1162 * Stores enough information to create a Group Description to add the group
1163 * to the switch by requesting the Group Service. Objects instantiating this
1164 * class are meant to be temporary and live as long as it is needed to wait for
1165 * preceding groups in the group chain to be created.
1166 */
1167 private class GroupChainElem {
1168 private GroupDescription groupDescription;
1169 private AtomicInteger waitOnGroups;
1170 private boolean addBucketToGroup;
1171
1172 GroupChainElem(GroupDescription groupDescription, int waitOnGroups,
1173 boolean addBucketToGroup) {
1174 this.groupDescription = groupDescription;
1175 this.waitOnGroups = new AtomicInteger(waitOnGroups);
1176 this.addBucketToGroup = addBucketToGroup;
1177 }
1178
1179 /**
1180 * This methods atomically decrements the counter for the number of
1181 * groups this GroupChainElement is waiting on, for notifications from
1182 * the Group Service. When this method returns a value of 0, this
1183 * GroupChainElement is ready to be processed.
1184 *
1185 * @return integer indication of the number of notifications being waited on
1186 */
1187 int decrementAndGetGroupsWaitedOn() {
1188 return waitOnGroups.decrementAndGet();
1189 }
1190
1191 @Override
1192 public String toString() {
1193 return (Integer.toHexString(groupDescription.givenGroupId()) +
1194 " groupKey: " + groupDescription.appCookie() +
1195 " waiting-on-groups: " + waitOnGroups.get() +
1196 " addBucketToGroup: " + addBucketToGroup +
1197 " device: " + deviceId);
1198 }
1199 }
1200}