blob: dd530800522d190081f9216acd15da4c1cc7546d [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;
Charles Chan5b9df8d2016-03-28 22:21:40 -07007import com.google.common.collect.ImmutableList;
8import com.google.common.collect.Lists;
Charles Chan188ebf52015-12-23 00:15:11 -08009import org.onlab.osgi.ServiceDirectory;
Charles Chan5b9df8d2016-03-28 22:21:40 -070010import org.onlab.packet.IpPrefix;
Charles Chan5270ed02016-01-30 23:22:37 -080011import org.onlab.packet.MacAddress;
Charles Chan188ebf52015-12-23 00:15:11 -080012import org.onlab.packet.MplsLabel;
13import org.onlab.packet.VlanId;
14import org.onosproject.core.ApplicationId;
15import org.onosproject.core.DefaultGroupId;
Charles Chan32562522016-04-07 14:37:14 -070016import org.onosproject.driver.extensions.OfdpaSetVlanVid;
Charles Chan188ebf52015-12-23 00:15:11 -080017import org.onosproject.net.DeviceId;
18import org.onosproject.net.PortNumber;
19import org.onosproject.net.behaviour.NextGroup;
20import org.onosproject.net.behaviour.PipelinerContext;
21import org.onosproject.net.flow.DefaultTrafficTreatment;
22import org.onosproject.net.flow.TrafficSelector;
23import org.onosproject.net.flow.TrafficTreatment;
24import org.onosproject.net.flow.criteria.Criterion;
25import org.onosproject.net.flow.criteria.VlanIdCriterion;
26import org.onosproject.net.flow.instructions.Instruction;
27import org.onosproject.net.flow.instructions.Instructions;
28import org.onosproject.net.flow.instructions.L2ModificationInstruction;
29import org.onosproject.net.flowobjective.FlowObjectiveStore;
30import org.onosproject.net.flowobjective.NextObjective;
31import org.onosproject.net.flowobjective.ObjectiveError;
32import org.onosproject.net.group.DefaultGroupBucket;
33import org.onosproject.net.group.DefaultGroupDescription;
34import org.onosproject.net.group.DefaultGroupKey;
35import org.onosproject.net.group.Group;
36import org.onosproject.net.group.GroupBucket;
37import org.onosproject.net.group.GroupBuckets;
38import org.onosproject.net.group.GroupDescription;
39import org.onosproject.net.group.GroupEvent;
40import org.onosproject.net.group.GroupKey;
41import org.onosproject.net.group.GroupListener;
42import org.onosproject.net.group.GroupService;
Saurav Das8be4e3a2016-03-11 17:19:07 -080043import org.onosproject.store.service.AtomicCounter;
44import org.onosproject.store.service.StorageService;
Charles Chan188ebf52015-12-23 00:15:11 -080045import org.slf4j.Logger;
46
47import java.util.ArrayDeque;
48import java.util.ArrayList;
49import java.util.Collection;
50import java.util.Collections;
51import java.util.Deque;
52import java.util.List;
53import java.util.Map;
Charles Chand0fd5dc2016-02-16 23:14:49 -080054import java.util.Objects;
Charles Chan188ebf52015-12-23 00:15:11 -080055import java.util.Set;
56import java.util.concurrent.ConcurrentHashMap;
57import java.util.concurrent.CopyOnWriteArrayList;
58import java.util.concurrent.Executors;
59import java.util.concurrent.ScheduledExecutorService;
60import java.util.concurrent.TimeUnit;
61import java.util.concurrent.atomic.AtomicInteger;
62import java.util.stream.Collectors;
63
64import static org.onlab.util.Tools.groupedThreads;
65import static org.slf4j.LoggerFactory.getLogger;
66
67/**
68 * Group handler for OFDPA2 pipeline.
69 */
Charles Chan361154b2016-03-24 10:23:39 -070070public class Ofdpa2GroupHandler {
Charles Chan188ebf52015-12-23 00:15:11 -080071 /*
72 * OFDPA requires group-id's to have a certain form.
73 * L2 Interface Groups have <4bits-0><12bits-vlanid><16bits-portid>
74 * L3 Unicast Groups have <4bits-2><28bits-index>
75 * MPLS Interface Groups have <4bits-9><4bits:0><24bits-index>
76 * L3 ECMP Groups have <4bits-7><28bits-index>
77 * L2 Flood Groups have <4bits-4><12bits-vlanid><16bits-index>
78 * L3 VPN Groups have <4bits-9><4bits-2><24bits-index>
79 */
Charles Chane849c192016-01-11 18:28:54 -080080 private static final int L2_INTERFACE_TYPE = 0x00000000;
Charles Chan5b9df8d2016-03-28 22:21:40 -070081 private static final int L3_INTERFACE_TYPE = 0x50000000;
Charles Chane849c192016-01-11 18:28:54 -080082 private static final int L3_UNICAST_TYPE = 0x20000000;
Charles Chan5b9df8d2016-03-28 22:21:40 -070083 private static final int L3_MULTICAST_TYPE = 0x60000000;
Charles Chane849c192016-01-11 18:28:54 -080084 private static final int MPLS_INTERFACE_TYPE = 0x90000000;
85 private static final int MPLS_L3VPN_SUBTYPE = 0x92000000;
86 private static final int L3_ECMP_TYPE = 0x70000000;
87 private static final int L2_FLOOD_TYPE = 0x40000000;
88
89 private static final int TYPE_MASK = 0x0fffffff;
90 private static final int SUBTYPE_MASK = 0x00ffffff;
Charles Chan5b9df8d2016-03-28 22:21:40 -070091 private static final int TYPE_VLAN_MASK = 0x0000ffff;
Charles Chane849c192016-01-11 18:28:54 -080092
93 private static final int PORT_LOWER_BITS_MASK = 0x3f;
94 private static final long PORT_HIGHER_BITS_MASK = ~PORT_LOWER_BITS_MASK;
Charles Chan188ebf52015-12-23 00:15:11 -080095
96 private final Logger log = getLogger(getClass());
97 private ServiceDirectory serviceDirectory;
98 protected GroupService groupService;
Saurav Das8be4e3a2016-03-11 17:19:07 -080099 protected StorageService storageService;
Charles Chan188ebf52015-12-23 00:15:11 -0800100
101 private DeviceId deviceId;
102 private FlowObjectiveStore flowObjectiveStore;
103 private Cache<GroupKey, List<OfdpaNextGroup>> pendingNextObjectives;
104 private ConcurrentHashMap<GroupKey, Set<GroupChainElem>> pendingGroups;
105 private ScheduledExecutorService groupChecker =
106 Executors.newScheduledThreadPool(2, groupedThreads("onos/pipeliner", "ofdpa2-%d"));
107
108 // index number for group creation
Saurav Das8be4e3a2016-03-11 17:19:07 -0800109 private AtomicCounter nextIndex;
Charles Chan188ebf52015-12-23 00:15:11 -0800110
111 // local stores for port-vlan mapping
112 protected Map<PortNumber, VlanId> port2Vlan = new ConcurrentHashMap<>();
113 protected Map<VlanId, Set<PortNumber>> vlan2Port = new ConcurrentHashMap<>();
114
115 // local store for pending bucketAdds - by design there can only be one
116 // pending bucket for a group
117 protected ConcurrentHashMap<Integer, NextObjective> pendingBuckets = new ConcurrentHashMap<>();
118
119 protected void init(DeviceId deviceId, PipelinerContext context) {
120 this.deviceId = deviceId;
121 this.flowObjectiveStore = context.store();
122 this.serviceDirectory = context.directory();
123 this.groupService = serviceDirectory.get(GroupService.class);
Saurav Das8be4e3a2016-03-11 17:19:07 -0800124 this.storageService = serviceDirectory.get(StorageService.class);
125 this.nextIndex = storageService.atomicCounterBuilder()
126 .withName("group-id-index-counter")
127 .build()
128 .asAtomicCounter();
Charles Chan188ebf52015-12-23 00:15:11 -0800129
130 pendingNextObjectives = CacheBuilder.newBuilder()
131 .expireAfterWrite(20, TimeUnit.SECONDS)
132 .removalListener((
133 RemovalNotification<GroupKey, List<OfdpaNextGroup>> notification) -> {
134 if (notification.getCause() == RemovalCause.EXPIRED) {
135 notification.getValue().forEach(ofdpaNextGrp ->
Charles Chan361154b2016-03-24 10:23:39 -0700136 Ofdpa2Pipeline.fail(ofdpaNextGrp.nextObj,
Charles Chan188ebf52015-12-23 00:15:11 -0800137 ObjectiveError.GROUPINSTALLATIONFAILED));
138
139 }
140 }).build();
141 pendingGroups = new ConcurrentHashMap<>();
142 groupChecker.scheduleAtFixedRate(new GroupChecker(), 0, 500, TimeUnit.MILLISECONDS);
143
144 groupService.addListener(new InnerGroupListener());
145 }
146
Saurav Das8be4e3a2016-03-11 17:19:07 -0800147 //////////////////////////////////////
148 // Group Creation
149 //////////////////////////////////////
150
Charles Chan188ebf52015-12-23 00:15:11 -0800151 protected void addGroup(NextObjective nextObjective) {
152 switch (nextObjective.type()) {
153 case SIMPLE:
154 Collection<TrafficTreatment> treatments = nextObjective.next();
155 if (treatments.size() != 1) {
156 log.error("Next Objectives of type Simple should only have a "
157 + "single Traffic Treatment. Next Objective Id:{}",
158 nextObjective.id());
Charles Chan361154b2016-03-24 10:23:39 -0700159 Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.BADPARAMS);
Charles Chan188ebf52015-12-23 00:15:11 -0800160 return;
161 }
162 processSimpleNextObjective(nextObjective);
163 break;
164 case BROADCAST:
165 processBroadcastNextObjective(nextObjective);
166 break;
167 case HASHED:
168 processHashedNextObjective(nextObjective);
169 break;
170 case FAILOVER:
Charles Chan361154b2016-03-24 10:23:39 -0700171 Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.UNSUPPORTED);
Charles Chan188ebf52015-12-23 00:15:11 -0800172 log.warn("Unsupported next objective type {}", nextObjective.type());
173 break;
174 default:
Charles Chan361154b2016-03-24 10:23:39 -0700175 Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.UNKNOWN);
Charles Chan188ebf52015-12-23 00:15:11 -0800176 log.warn("Unknown next objective type {}", nextObjective.type());
177 }
178 }
179
180 /**
181 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
182 * a chain of groups. The simple Next Objective passed
183 * in by the application has to be broken up into a group chain
184 * comprising of an L3 Unicast Group that points to an L2 Interface
185 * Group which in-turn points to an output port. In some cases, the simple
186 * next Objective can just be an L2 interface without the need for chaining.
187 *
188 * @param nextObj the nextObjective of type SIMPLE
189 */
190 private void processSimpleNextObjective(NextObjective nextObj) {
191 TrafficTreatment treatment = nextObj.next().iterator().next();
192 // determine if plain L2 or L3->L2
193 boolean plainL2 = true;
194 for (Instruction ins : treatment.allInstructions()) {
195 if (ins.type() == Instruction.Type.L2MODIFICATION) {
196 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
197 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.ETH_DST ||
198 l2ins.subtype() == L2ModificationInstruction.L2SubType.ETH_SRC) {
199 plainL2 = false;
200 break;
201 }
202 }
203 }
204
205 if (plainL2) {
206 createL2InterfaceGroup(nextObj);
207 return;
208 }
209
210 // break up simple next objective to GroupChain objects
211 GroupInfo groupInfo = createL2L3Chain(treatment, nextObj.id(),
Saurav Das8be4e3a2016-03-11 17:19:07 -0800212 nextObj.appId(), false,
213 nextObj.meta());
Charles Chan188ebf52015-12-23 00:15:11 -0800214 if (groupInfo == null) {
215 log.error("Could not process nextObj={} in dev:{}", nextObj.id(), deviceId);
216 return;
217 }
218 // create object for local and distributed storage
219 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700220 gkeyChain.addFirst(groupInfo.innerMostGroupDesc.appCookie());
221 gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
Charles Chan188ebf52015-12-23 00:15:11 -0800222 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(
223 Collections.singletonList(gkeyChain),
224 nextObj);
225
226 // store l3groupkey with the ofdpaNextGroup for the nextObjective that depends on it
Charles Chan5b9df8d2016-03-28 22:21:40 -0700227 updatePendingNextObjective(groupInfo.nextGroupDesc.appCookie(), ofdpaGrp);
Charles Chan188ebf52015-12-23 00:15:11 -0800228
229 // now we are ready to send the l2 groupDescription (inner), as all the stores
230 // that will get async replies have been updated. By waiting to update
231 // the stores, we prevent nasty race conditions.
Charles Chan5b9df8d2016-03-28 22:21:40 -0700232 groupService.addGroup(groupInfo.innerMostGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800233 }
234
Charles Chan188ebf52015-12-23 00:15:11 -0800235 /**
236 * Creates a simple L2 Interface Group.
237 *
238 * @param nextObj the next Objective
239 */
240 private void createL2InterfaceGroup(NextObjective nextObj) {
Charles Chan5b9df8d2016-03-28 22:21:40 -0700241 VlanId assignedVlan = Ofdpa2Pipeline.readVlanFromSelector(nextObj.meta());
242 if (assignedVlan == null) {
243 log.warn("VLAN ID required by simple next obj is missing. Abort.");
244 Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
Charles Chan188ebf52015-12-23 00:15:11 -0800245 return;
246 }
247
Charles Chan5b9df8d2016-03-28 22:21:40 -0700248 List<GroupInfo> groupInfos = prepareL2InterfaceGroup(nextObj, assignedVlan);
Charles Chan188ebf52015-12-23 00:15:11 -0800249
Charles Chan5b9df8d2016-03-28 22:21:40 -0700250 // There is only one L2 interface group in this case
251 GroupDescription l2InterfaceGroupDesc = groupInfos.get(0).innerMostGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -0800252
Charles Chan5b9df8d2016-03-28 22:21:40 -0700253 // Put all dependency information into allGroupKeys
254 List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
255 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
256 gkeyChain.addFirst(l2InterfaceGroupDesc.appCookie());
257 allGroupKeys.add(gkeyChain);
Charles Chan188ebf52015-12-23 00:15:11 -0800258
Charles Chan5b9df8d2016-03-28 22:21:40 -0700259 // Point the next objective to this group
260 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
261 updatePendingNextObjective(l2InterfaceGroupDesc.appCookie(), ofdpaGrp);
262
263 // Start installing the inner-most group
264 groupService.addGroup(l2InterfaceGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800265 }
266
267 /**
268 * Creates one of two possible group-chains from the treatment
269 * passed in. Depending on the MPLS boolean, this method either creates
270 * an L3Unicast Group --> L2Interface Group, if mpls is false;
271 * or MPLSInterface Group --> L2Interface Group, if mpls is true;
272 * The returned 'inner' group description is always the L2 Interface group.
273 *
274 * @param treatment that needs to be broken up to create the group chain
275 * @param nextId of the next objective that needs this group chain
276 * @param appId of the application that sent this next objective
277 * @param mpls determines if L3Unicast or MPLSInterface group is created
278 * @param meta metadata passed in by the application as part of the nextObjective
279 * @return GroupInfo containing the GroupDescription of the
280 * L2Interface group(inner) and the GroupDescription of the (outer)
281 * L3Unicast/MPLSInterface group. May return null if there is an
282 * error in processing the chain
283 */
284 private GroupInfo createL2L3Chain(TrafficTreatment treatment, int nextId,
Saurav Das8be4e3a2016-03-11 17:19:07 -0800285 ApplicationId appId, boolean mpls,
286 TrafficSelector meta) {
Charles Chan188ebf52015-12-23 00:15:11 -0800287 // for the l2interface group, get vlan and port info
288 // for the outer group, get the src/dst mac, and vlan info
289 TrafficTreatment.Builder outerTtb = DefaultTrafficTreatment.builder();
290 TrafficTreatment.Builder innerTtb = DefaultTrafficTreatment.builder();
291 VlanId vlanid = null;
292 long portNum = 0;
293 boolean setVlan = false, popVlan = false;
Charles Chand0fd5dc2016-02-16 23:14:49 -0800294 MacAddress srcMac = MacAddress.ZERO;
Charles Chan5270ed02016-01-30 23:22:37 -0800295 MacAddress dstMac = MacAddress.ZERO;
Charles Chan188ebf52015-12-23 00:15:11 -0800296 for (Instruction ins : treatment.allInstructions()) {
297 if (ins.type() == Instruction.Type.L2MODIFICATION) {
298 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
299 switch (l2ins.subtype()) {
300 case ETH_DST:
Charles Chan5270ed02016-01-30 23:22:37 -0800301 dstMac = ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac();
302 outerTtb.setEthDst(dstMac);
Charles Chan188ebf52015-12-23 00:15:11 -0800303 break;
304 case ETH_SRC:
Charles Chand0fd5dc2016-02-16 23:14:49 -0800305 srcMac = ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac();
306 outerTtb.setEthSrc(srcMac);
Charles Chan188ebf52015-12-23 00:15:11 -0800307 break;
308 case VLAN_ID:
309 vlanid = ((L2ModificationInstruction.ModVlanIdInstruction) l2ins).vlanId();
Charles Chan32562522016-04-07 14:37:14 -0700310 OfdpaSetVlanVid ofdpaSetVlanVid = new OfdpaSetVlanVid(vlanid);
311 outerTtb.extension(ofdpaSetVlanVid, deviceId);
Charles Chan188ebf52015-12-23 00:15:11 -0800312 setVlan = true;
313 break;
314 case VLAN_POP:
315 innerTtb.popVlan();
316 popVlan = true;
317 break;
318 case DEC_MPLS_TTL:
319 case MPLS_LABEL:
320 case MPLS_POP:
321 case MPLS_PUSH:
322 case VLAN_PCP:
323 case VLAN_PUSH:
324 default:
325 break;
326 }
327 } else if (ins.type() == Instruction.Type.OUTPUT) {
328 portNum = ((Instructions.OutputInstruction) ins).port().toLong();
329 innerTtb.add(ins);
330 } else {
331 log.warn("Driver does not handle this type of TrafficTreatment"
332 + " instruction in nextObjectives: {}", ins.type());
333 }
334 }
335
336 if (vlanid == null && meta != null) {
337 // use metadata if available
338 Criterion vidCriterion = meta.getCriterion(Criterion.Type.VLAN_VID);
339 if (vidCriterion != null) {
340 vlanid = ((VlanIdCriterion) vidCriterion).vlanId();
341 }
342 // if vlan is not set, use the vlan in metadata for outerTtb
343 if (vlanid != null && !setVlan) {
Charles Chan32562522016-04-07 14:37:14 -0700344 OfdpaSetVlanVid ofdpaSetVlanVid = new OfdpaSetVlanVid(vlanid);
345 outerTtb.extension(ofdpaSetVlanVid, deviceId);
Charles Chan188ebf52015-12-23 00:15:11 -0800346 }
347 }
348
349 if (vlanid == null) {
350 log.error("Driver cannot process an L2/L3 group chain without "
351 + "egress vlan information for dev: {} port:{}",
352 deviceId, portNum);
353 return null;
354 }
355
356 if (!setVlan && !popVlan) {
357 // untagged outgoing port
358 TrafficTreatment.Builder temp = DefaultTrafficTreatment.builder();
359 temp.popVlan();
360 innerTtb.build().allInstructions().forEach(i -> temp.add(i));
361 innerTtb = temp;
362 }
363
364 // assemble information for ofdpa l2interface group
Charles Chane849c192016-01-11 18:28:54 -0800365 int l2groupId = L2_INTERFACE_TYPE | (vlanid.toShort() << 16) | (int) portNum;
Saurav Das8be4e3a2016-03-11 17:19:07 -0800366 // a globally unique groupkey that is different for ports in the same device,
Charles Chan188ebf52015-12-23 00:15:11 -0800367 // but different for the same portnumber on different devices. Also different
368 // for the various group-types created out of the same next objective.
Charles Chane849c192016-01-11 18:28:54 -0800369 int l2gk = l2InterfaceGroupKey(deviceId, vlanid, portNum);
Charles Chan361154b2016-03-24 10:23:39 -0700370 final GroupKey l2groupkey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l2gk));
Charles Chan188ebf52015-12-23 00:15:11 -0800371
372 // assemble information for outer group
373 GroupDescription outerGrpDesc = null;
374 if (mpls) {
375 // outer group is MPLSInteface
Saurav Das8be4e3a2016-03-11 17:19:07 -0800376 int mplsInterfaceIndex = getNextAvailableIndex();
377 int mplsgroupId = MPLS_INTERFACE_TYPE | (SUBTYPE_MASK & mplsInterfaceIndex);
378 final GroupKey mplsgroupkey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700379 Ofdpa2Pipeline.appKryo.serialize(mplsInterfaceIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800380 outerTtb.group(new DefaultGroupId(l2groupId));
381 // create the mpls-interface group description to wait for the
382 // l2 interface group to be processed
383 GroupBucket mplsinterfaceGroupBucket =
384 DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
385 outerGrpDesc = new DefaultGroupDescription(
386 deviceId,
387 GroupDescription.Type.INDIRECT,
388 new GroupBuckets(Collections.singletonList(
389 mplsinterfaceGroupBucket)),
390 mplsgroupkey,
391 mplsgroupId,
392 appId);
393 log.debug("Trying MPLS-Interface: device:{} gid:{} gkey:{} nextid:{}",
394 deviceId, Integer.toHexString(mplsgroupId),
395 mplsgroupkey, nextId);
396 } else {
397 // outer group is L3Unicast
Saurav Das8be4e3a2016-03-11 17:19:07 -0800398 int l3unicastIndex = getNextAvailableIndex();
399 int l3groupId = L3_UNICAST_TYPE | (TYPE_MASK & l3unicastIndex);
400 final GroupKey l3groupkey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700401 Ofdpa2Pipeline.appKryo.serialize(l3unicastIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800402 outerTtb.group(new DefaultGroupId(l2groupId));
403 // create the l3unicast group description to wait for the
404 // l2 interface group to be processed
405 GroupBucket l3unicastGroupBucket =
406 DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
407 outerGrpDesc = new DefaultGroupDescription(
408 deviceId,
409 GroupDescription.Type.INDIRECT,
410 new GroupBuckets(Collections.singletonList(
411 l3unicastGroupBucket)),
412 l3groupkey,
413 l3groupId,
414 appId);
415 log.debug("Trying L3Unicast: device:{} gid:{} gkey:{} nextid:{}",
416 deviceId, Integer.toHexString(l3groupId),
417 l3groupkey, nextId);
418 }
419
420 // store l2groupkey with the groupChainElem for the outer-group that depends on it
421 GroupChainElem gce = new GroupChainElem(outerGrpDesc, 1, false);
422 updatePendingGroups(l2groupkey, gce);
423
424 // create group description for the inner l2interfacegroup
Charles Chan5b9df8d2016-03-28 22:21:40 -0700425 GroupBucket l2InterfaceGroupBucket =
Charles Chan188ebf52015-12-23 00:15:11 -0800426 DefaultGroupBucket.createIndirectGroupBucket(innerTtb.build());
427 GroupDescription l2groupDescription =
428 new DefaultGroupDescription(
429 deviceId,
430 GroupDescription.Type.INDIRECT,
431 new GroupBuckets(Collections.singletonList(
Charles Chan5b9df8d2016-03-28 22:21:40 -0700432 l2InterfaceGroupBucket)),
Charles Chan188ebf52015-12-23 00:15:11 -0800433 l2groupkey,
434 l2groupId,
435 appId);
436 log.debug("Trying L2Interface: device:{} gid:{} gkey:{} nextId:{}",
437 deviceId, Integer.toHexString(l2groupId),
438 l2groupkey, nextId);
439 return new GroupInfo(l2groupDescription, outerGrpDesc);
440
441 }
442
443 /**
444 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
445 * a chain of groups. The broadcast Next Objective passed in by the application
446 * has to be broken up into a group chain comprising of an
447 * L2 Flood group whose buckets point to L2 Interface groups.
448 *
449 * @param nextObj the nextObjective of type BROADCAST
450 */
451 private void processBroadcastNextObjective(NextObjective nextObj) {
Charles Chan5b9df8d2016-03-28 22:21:40 -0700452 VlanId assignedVlan = Ofdpa2Pipeline.readVlanFromSelector(nextObj.meta());
453 if (assignedVlan == null) {
454 log.warn("VLAN ID required by broadcast next obj is missing. Abort.");
455 Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
Charles Chan188ebf52015-12-23 00:15:11 -0800456 return;
457 }
Charles Chan188ebf52015-12-23 00:15:11 -0800458
Charles Chan5b9df8d2016-03-28 22:21:40 -0700459 List<GroupInfo> groupInfos = prepareL2InterfaceGroup(nextObj, assignedVlan);
460
461 IpPrefix ipDst = Ofdpa2Pipeline.readIpDstFromSelector(nextObj.meta());
462 if (ipDst != null) {
463 if (ipDst.isMulticast()) {
464 createL3MulticastGroup(nextObj, assignedVlan, groupInfos);
465 } else {
466 log.warn("Broadcast NextObj with non-multicast IP address {}", nextObj);
467 Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
468 return;
469 }
470 } else {
471 createL2FloodGroup(nextObj, assignedVlan, groupInfos);
472 }
473 }
474
475 private List<GroupInfo> prepareL2InterfaceGroup(NextObjective nextObj, VlanId assignedVlan) {
476 ImmutableList.Builder<GroupInfo> groupInfoBuilder = ImmutableList.builder();
477
478 // break up broadcast next objective to multiple groups
479 Collection<TrafficTreatment> buckets = nextObj.next();
480
Charles Chan188ebf52015-12-23 00:15:11 -0800481 // each treatment is converted to an L2 interface group
Charles Chan188ebf52015-12-23 00:15:11 -0800482 for (TrafficTreatment treatment : buckets) {
483 TrafficTreatment.Builder newTreatment = DefaultTrafficTreatment.builder();
484 PortNumber portNum = null;
Charles Chan5b9df8d2016-03-28 22:21:40 -0700485 VlanId egressVlan = null;
Charles Chan188ebf52015-12-23 00:15:11 -0800486 // ensure that the only allowed treatments are pop-vlan and output
487 for (Instruction ins : treatment.allInstructions()) {
488 if (ins.type() == Instruction.Type.L2MODIFICATION) {
489 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
490 switch (l2ins.subtype()) {
491 case VLAN_POP:
492 newTreatment.add(l2ins);
493 break;
Charles Chan5b9df8d2016-03-28 22:21:40 -0700494 case VLAN_ID:
495 egressVlan = ((L2ModificationInstruction.ModVlanIdInstruction) l2ins).vlanId();
496 break;
Charles Chan188ebf52015-12-23 00:15:11 -0800497 default:
498 log.debug("action {} not permitted for broadcast nextObj",
499 l2ins.subtype());
500 break;
501 }
502 } else if (ins.type() == Instruction.Type.OUTPUT) {
503 portNum = ((Instructions.OutputInstruction) ins).port();
504 newTreatment.add(ins);
505 } else {
Charles Chane849c192016-01-11 18:28:54 -0800506 log.debug("TrafficTreatment of type {} not permitted in " +
507 " broadcast nextObjective", ins.type());
Charles Chan188ebf52015-12-23 00:15:11 -0800508 }
509 }
510
Charles Chan188ebf52015-12-23 00:15:11 -0800511 // assemble info for l2 interface group
Charles Chan5b9df8d2016-03-28 22:21:40 -0700512 VlanId l2InterfaceGroupVlan =
513 (egressVlan != null && !assignedVlan.equals(egressVlan)) ?
514 egressVlan : assignedVlan;
515 int l2gk = l2InterfaceGroupKey(deviceId, l2InterfaceGroupVlan, portNum.toLong());
516 final GroupKey l2InterfaceGroupKey =
517 new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l2gk));
518 int l2InterfaceGroupId = L2_INTERFACE_TYPE | (l2InterfaceGroupVlan.toShort() << 16) |
Charles Chan188ebf52015-12-23 00:15:11 -0800519 (int) portNum.toLong();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700520 GroupBucket l2InterfaceGroupBucket =
Charles Chan188ebf52015-12-23 00:15:11 -0800521 DefaultGroupBucket.createIndirectGroupBucket(newTreatment.build());
Charles Chan5b9df8d2016-03-28 22:21:40 -0700522 GroupDescription l2InterfaceGroupDescription =
Charles Chan188ebf52015-12-23 00:15:11 -0800523 new DefaultGroupDescription(
524 deviceId,
525 GroupDescription.Type.INDIRECT,
526 new GroupBuckets(Collections.singletonList(
Charles Chan5b9df8d2016-03-28 22:21:40 -0700527 l2InterfaceGroupBucket)),
528 l2InterfaceGroupKey,
529 l2InterfaceGroupId,
Charles Chan188ebf52015-12-23 00:15:11 -0800530 nextObj.appId());
531 log.debug("Trying L2-Interface: device:{} gid:{} gkey:{} nextid:{}",
Charles Chan5b9df8d2016-03-28 22:21:40 -0700532 deviceId, Integer.toHexString(l2InterfaceGroupId),
533 l2InterfaceGroupKey, nextObj.id());
Charles Chan188ebf52015-12-23 00:15:11 -0800534
Charles Chan5b9df8d2016-03-28 22:21:40 -0700535 groupInfoBuilder.add(new GroupInfo(l2InterfaceGroupDescription,
536 l2InterfaceGroupDescription));
Charles Chan188ebf52015-12-23 00:15:11 -0800537 }
Charles Chan5b9df8d2016-03-28 22:21:40 -0700538 return groupInfoBuilder.build();
539 }
Charles Chan188ebf52015-12-23 00:15:11 -0800540
Charles Chan5b9df8d2016-03-28 22:21:40 -0700541 private void createL2FloodGroup(NextObjective nextObj, VlanId vlanId, List<GroupInfo> groupInfos) {
Charles Chan188ebf52015-12-23 00:15:11 -0800542 // assemble info for l2 flood group
Saurav Das0fd79d92016-03-07 10:58:36 -0800543 // since there can be only one flood group for a vlan, its index is always the same - 0
544 Integer l2floodgroupId = L2_FLOOD_TYPE | (vlanId.toShort() << 16);
Saurav Das8be4e3a2016-03-11 17:19:07 -0800545 int l2floodgk = getNextAvailableIndex();
Charles Chan361154b2016-03-24 10:23:39 -0700546 final GroupKey l2floodgroupkey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l2floodgk));
Charles Chan5b9df8d2016-03-28 22:21:40 -0700547
Charles Chan188ebf52015-12-23 00:15:11 -0800548 // collection of group buckets pointing to all the l2 interface groups
Charles Chan5b9df8d2016-03-28 22:21:40 -0700549 List<GroupBucket> l2floodBuckets = Lists.newArrayList();
550 groupInfos.forEach(groupInfo -> {
551 GroupDescription l2intGrpDesc = groupInfo.nextGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -0800552 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
553 ttb.group(new DefaultGroupId(l2intGrpDesc.givenGroupId()));
554 GroupBucket abucket = DefaultGroupBucket.createAllGroupBucket(ttb.build());
555 l2floodBuckets.add(abucket);
Charles Chan5b9df8d2016-03-28 22:21:40 -0700556 });
Charles Chan188ebf52015-12-23 00:15:11 -0800557 // create the l2flood group-description to wait for all the
558 // l2interface groups to be processed
559 GroupDescription l2floodGroupDescription =
560 new DefaultGroupDescription(
561 deviceId,
562 GroupDescription.Type.ALL,
563 new GroupBuckets(l2floodBuckets),
564 l2floodgroupkey,
565 l2floodgroupId,
566 nextObj.appId());
Charles Chan188ebf52015-12-23 00:15:11 -0800567 log.debug("Trying L2-Flood: device:{} gid:{} gkey:{} nextid:{}",
568 deviceId, Integer.toHexString(l2floodgroupId),
569 l2floodgroupkey, nextObj.id());
570
Charles Chan5b9df8d2016-03-28 22:21:40 -0700571 // Put all dependency information into allGroupKeys
572 List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
573 groupInfos.forEach(groupInfo -> {
574 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
575 // In this case we should have L2 interface group only
576 gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
577 gkeyChain.addFirst(l2floodgroupkey);
578 allGroupKeys.add(gkeyChain);
579 });
Charles Chan188ebf52015-12-23 00:15:11 -0800580
Charles Chan5b9df8d2016-03-28 22:21:40 -0700581 // Point the next objective to this group
582 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
Charles Chan188ebf52015-12-23 00:15:11 -0800583 updatePendingNextObjective(l2floodgroupkey, ofdpaGrp);
584
Charles Chan5b9df8d2016-03-28 22:21:40 -0700585 GroupChainElem gce = new GroupChainElem(l2floodGroupDescription,
586 groupInfos.size(), false);
587 groupInfos.forEach(groupInfo -> {
588 // Point this group to the next group
589 updatePendingGroups(groupInfo.nextGroupDesc.appCookie(), gce);
590 // Start installing the inner-most group
591 groupService.addGroup(groupInfo.innerMostGroupDesc);
592 });
593 }
594
595 private void createL3MulticastGroup(NextObjective nextObj, VlanId vlanId, List<GroupInfo> groupInfos) {
596 List<GroupBucket> l3McastBuckets = new ArrayList<>();
597 groupInfos.forEach(groupInfo -> {
598 // Points to L3 interface group if there is one.
599 // Otherwise points to L2 interface group directly.
600 GroupDescription nextGroupDesc = (groupInfo.nextGroupDesc != null) ?
601 groupInfo.nextGroupDesc : groupInfo.innerMostGroupDesc;
602 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
603 ttb.group(new DefaultGroupId(nextGroupDesc.givenGroupId()));
604 GroupBucket abucket = DefaultGroupBucket.createAllGroupBucket(ttb.build());
605 l3McastBuckets.add(abucket);
606 });
607
608 int l3MulticastIndex = getNextAvailableIndex();
609 int l3MulticastGroupId = L3_MULTICAST_TYPE | vlanId.toShort() << 16 | (TYPE_VLAN_MASK & l3MulticastIndex);
610 final GroupKey l3MulticastGroupKey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l3MulticastIndex));
611
612 GroupDescription l3MulticastGroupDesc = new DefaultGroupDescription(deviceId,
613 GroupDescription.Type.ALL,
614 new GroupBuckets(l3McastBuckets),
615 l3MulticastGroupKey,
616 l3MulticastGroupId,
617 nextObj.appId());
618
619 // Put all dependency information into allGroupKeys
620 List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
621 groupInfos.forEach(groupInfo -> {
622 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
623 gkeyChain.addFirst(groupInfo.innerMostGroupDesc.appCookie());
624 // Add L3 interface group to the chain if there is one.
625 if (!groupInfo.nextGroupDesc.equals(groupInfo.innerMostGroupDesc)) {
626 gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
627 }
628 gkeyChain.addFirst(l3MulticastGroupKey);
629 allGroupKeys.add(gkeyChain);
630 });
631
632 // Point the next objective to this group
633 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
634 updatePendingNextObjective(l3MulticastGroupKey, ofdpaGrp);
635
636 GroupChainElem outerGce = new GroupChainElem(l3MulticastGroupDesc,
637 groupInfos.size(), false);
638 groupInfos.forEach(groupInfo -> {
639 // Point this group (L3 multicast) to the next group
640 updatePendingGroups(groupInfo.nextGroupDesc.appCookie(), outerGce);
641
642 // Point next group to inner-most group, if any
643 if (!groupInfo.nextGroupDesc.equals(groupInfo.innerMostGroupDesc)) {
644 GroupChainElem innerGce = new GroupChainElem(groupInfo.nextGroupDesc,
645 1, false);
646 updatePendingGroups(groupInfo.innerMostGroupDesc.appCookie(), innerGce);
647 }
648
649 // Start installing the inner-most group
650 groupService.addGroup(groupInfo.innerMostGroupDesc);
651 });
Charles Chan188ebf52015-12-23 00:15:11 -0800652 }
653
Charles Chan188ebf52015-12-23 00:15:11 -0800654 /**
655 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
656 * a chain of groups. The hashed Next Objective passed in by the application
657 * has to be broken up into a group chain comprising of an
658 * L3 ECMP group as the top level group. Buckets of this group can point
659 * to a variety of groups in a group chain, depending on the whether
660 * MPLS labels are being pushed or not.
661 * <p>
662 * NOTE: We do not create MPLS ECMP groups as they are unimplemented in
663 * OF-DPA 2.0 (even though it is in the spec). Therefore we do not
664 * check the nextObjective meta to see what is matching before being
665 * sent to this nextObjective.
666 *
667 * @param nextObj the nextObjective of type HASHED
668 */
669 private void processHashedNextObjective(NextObjective nextObj) {
670 // storage for all group keys in the chain of groups created
671 List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
672 List<GroupInfo> unsentGroups = new ArrayList<>();
673 createHashBucketChains(nextObj, allGroupKeys, unsentGroups);
674
675 // now we can create the outermost L3 ECMP group
676 List<GroupBucket> l3ecmpGroupBuckets = new ArrayList<>();
677 for (GroupInfo gi : unsentGroups) {
678 // create ECMP bucket to point to the outer group
679 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700680 ttb.group(new DefaultGroupId(gi.nextGroupDesc.givenGroupId()));
Charles Chan188ebf52015-12-23 00:15:11 -0800681 GroupBucket sbucket = DefaultGroupBucket
682 .createSelectGroupBucket(ttb.build());
683 l3ecmpGroupBuckets.add(sbucket);
684 }
Saurav Das8be4e3a2016-03-11 17:19:07 -0800685 int l3ecmpIndex = getNextAvailableIndex();
686 int l3ecmpGroupId = L3_ECMP_TYPE | (TYPE_MASK & l3ecmpIndex);
687 GroupKey l3ecmpGroupKey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700688 Ofdpa2Pipeline.appKryo.serialize(l3ecmpIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800689 GroupDescription l3ecmpGroupDesc =
690 new DefaultGroupDescription(
691 deviceId,
692 GroupDescription.Type.SELECT,
693 new GroupBuckets(l3ecmpGroupBuckets),
694 l3ecmpGroupKey,
695 l3ecmpGroupId,
696 nextObj.appId());
697 GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc,
698 l3ecmpGroupBuckets.size(),
699 false);
700
701 // create objects for local and distributed storage
702 allGroupKeys.forEach(gkeyChain -> gkeyChain.addFirst(l3ecmpGroupKey));
703 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
704
705 // store l3ecmpGroupKey with the ofdpaGroupChain for the nextObjective
706 // that depends on it
707 updatePendingNextObjective(l3ecmpGroupKey, ofdpaGrp);
708
709 log.debug("Trying L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
710 deviceId, Integer.toHexString(l3ecmpGroupId),
711 l3ecmpGroupKey, nextObj.id());
712 // finally we are ready to send the innermost groups
713 for (GroupInfo gi : unsentGroups) {
714 log.debug("Sending innermost group {} in group chain on device {} ",
Charles Chan5b9df8d2016-03-28 22:21:40 -0700715 Integer.toHexString(gi.innerMostGroupDesc.givenGroupId()), deviceId);
716 updatePendingGroups(gi.nextGroupDesc.appCookie(), l3ecmpGce);
717 groupService.addGroup(gi.innerMostGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800718 }
719
720 }
721
722 /**
723 * Creates group chains for all buckets in a hashed group, and stores the
724 * GroupInfos and GroupKeys for all the groups in the lists passed in, which
725 * should be empty.
726 * <p>
727 * Does not create the top level ECMP group. Does not actually send the
728 * groups to the groupService.
729 *
730 * @param nextObj the Next Objective with buckets that need to be converted
731 * to group chains
732 * @param allGroupKeys a list to store groupKey for each bucket-group-chain
733 * @param unsentGroups a list to store GroupInfo for each bucket-group-chain
734 */
735 private void createHashBucketChains(NextObjective nextObj,
Saurav Das8be4e3a2016-03-11 17:19:07 -0800736 List<Deque<GroupKey>> allGroupKeys,
737 List<GroupInfo> unsentGroups) {
Charles Chan188ebf52015-12-23 00:15:11 -0800738 // break up hashed next objective to multiple groups
739 Collection<TrafficTreatment> buckets = nextObj.next();
740
741 for (TrafficTreatment bucket : buckets) {
742 //figure out how many labels are pushed in each bucket
743 int labelsPushed = 0;
744 MplsLabel innermostLabel = null;
745 for (Instruction ins : bucket.allInstructions()) {
746 if (ins.type() == Instruction.Type.L2MODIFICATION) {
747 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
748 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.MPLS_PUSH) {
749 labelsPushed++;
750 }
751 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.MPLS_LABEL) {
752 if (innermostLabel == null) {
Ray Milkey125572b2016-02-22 16:48:17 -0800753 innermostLabel = ((L2ModificationInstruction.ModMplsLabelInstruction) l2ins).label();
Charles Chan188ebf52015-12-23 00:15:11 -0800754 }
755 }
756 }
757 }
758
759 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
760 // XXX we only deal with 0 and 1 label push right now
761 if (labelsPushed == 0) {
762 GroupInfo nolabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
763 nextObj.appId(), false,
764 nextObj.meta());
765 if (nolabelGroupInfo == null) {
766 log.error("Could not process nextObj={} in dev:{}",
767 nextObj.id(), deviceId);
768 return;
769 }
Charles Chan5b9df8d2016-03-28 22:21:40 -0700770 gkeyChain.addFirst(nolabelGroupInfo.innerMostGroupDesc.appCookie());
771 gkeyChain.addFirst(nolabelGroupInfo.nextGroupDesc.appCookie());
Charles Chan188ebf52015-12-23 00:15:11 -0800772
773 // we can't send the inner group description yet, as we have to
774 // create the dependent ECMP group first. So we store..
775 unsentGroups.add(nolabelGroupInfo);
776
777 } else if (labelsPushed == 1) {
778 GroupInfo onelabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
779 nextObj.appId(), true,
780 nextObj.meta());
781 if (onelabelGroupInfo == null) {
782 log.error("Could not process nextObj={} in dev:{}",
783 nextObj.id(), deviceId);
784 return;
785 }
786 // we need to add another group to this chain - the L3VPN group
787 TrafficTreatment.Builder l3vpnTtb = DefaultTrafficTreatment.builder();
788 l3vpnTtb.pushMpls()
789 .setMpls(innermostLabel)
790 .setMplsBos(true)
791 .copyTtlOut()
792 .group(new DefaultGroupId(
Charles Chan5b9df8d2016-03-28 22:21:40 -0700793 onelabelGroupInfo.nextGroupDesc.givenGroupId()));
Charles Chan188ebf52015-12-23 00:15:11 -0800794 GroupBucket l3vpnGrpBkt =
795 DefaultGroupBucket.createIndirectGroupBucket(l3vpnTtb.build());
Saurav Das8be4e3a2016-03-11 17:19:07 -0800796 int l3vpnIndex = getNextAvailableIndex();
797 int l3vpngroupId = MPLS_L3VPN_SUBTYPE | (SUBTYPE_MASK & l3vpnIndex);
798 GroupKey l3vpngroupkey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700799 Ofdpa2Pipeline.appKryo.serialize(l3vpnIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800800 GroupDescription l3vpnGroupDesc =
801 new DefaultGroupDescription(
802 deviceId,
803 GroupDescription.Type.INDIRECT,
804 new GroupBuckets(Collections.singletonList(
805 l3vpnGrpBkt)),
806 l3vpngroupkey,
807 l3vpngroupId,
808 nextObj.appId());
809 GroupChainElem l3vpnGce = new GroupChainElem(l3vpnGroupDesc, 1, false);
Charles Chan5b9df8d2016-03-28 22:21:40 -0700810 updatePendingGroups(onelabelGroupInfo.nextGroupDesc.appCookie(), l3vpnGce);
Charles Chan188ebf52015-12-23 00:15:11 -0800811
Charles Chan5b9df8d2016-03-28 22:21:40 -0700812 gkeyChain.addFirst(onelabelGroupInfo.innerMostGroupDesc.appCookie());
813 gkeyChain.addFirst(onelabelGroupInfo.nextGroupDesc.appCookie());
Charles Chan188ebf52015-12-23 00:15:11 -0800814 gkeyChain.addFirst(l3vpngroupkey);
815
816 //now we can replace the outerGrpDesc with the one we just created
Charles Chan5b9df8d2016-03-28 22:21:40 -0700817 onelabelGroupInfo.nextGroupDesc = l3vpnGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -0800818
819 // we can't send the innermost group yet, as we have to create
820 // the dependent ECMP group first. So we store ...
821 unsentGroups.add(onelabelGroupInfo);
822
823 log.debug("Trying L3VPN: device:{} gid:{} gkey:{} nextId:{}",
824 deviceId, Integer.toHexString(l3vpngroupId),
825 l3vpngroupkey, nextObj.id());
826
827 } else {
828 log.warn("Driver currently does not handle more than 1 MPLS "
829 + "labels. Not processing nextObjective {}", nextObj.id());
830 return;
831 }
832
833 // all groups in this chain
834 allGroupKeys.add(gkeyChain);
835 }
836 }
837
Saurav Das8be4e3a2016-03-11 17:19:07 -0800838 //////////////////////////////////////
839 // Group Editing
840 //////////////////////////////////////
841
Charles Chan188ebf52015-12-23 00:15:11 -0800842 /**
843 * Adds a bucket to the top level group of a group-chain, and creates the chain.
844 *
845 * @param nextObjective the next group to add a bucket to
846 * @param next the representation of the existing group-chain for this next objective
847 */
848 protected void addBucketToGroup(NextObjective nextObjective, NextGroup next) {
849 if (nextObjective.type() != NextObjective.Type.HASHED) {
850 log.warn("AddBuckets not applied to nextType:{} in dev:{} for next:{}",
851 nextObjective.type(), deviceId, nextObjective.id());
852 return;
853 }
854 if (nextObjective.next().size() > 1) {
855 log.warn("Only one bucket can be added at a time");
856 return;
857 }
858 // storage for all group keys in the chain of groups created
859 List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
860 List<GroupInfo> unsentGroups = new ArrayList<>();
861 createHashBucketChains(nextObjective, allGroupKeys, unsentGroups);
862
863 // now we can create the outermost L3 ECMP group bucket to add
864 GroupInfo gi = unsentGroups.get(0); // only one bucket, so only one group-chain
865 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700866 ttb.group(new DefaultGroupId(gi.nextGroupDesc.givenGroupId()));
Charles Chan188ebf52015-12-23 00:15:11 -0800867 GroupBucket sbucket = DefaultGroupBucket.createSelectGroupBucket(ttb.build());
868
869 // recreate the original L3 ECMP group id and description
Charles Chane849c192016-01-11 18:28:54 -0800870 int l3ecmpGroupId = L3_ECMP_TYPE | nextObjective.id() << 12;
Charles Chan361154b2016-03-24 10:23:39 -0700871 GroupKey l3ecmpGroupKey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l3ecmpGroupId));
Charles Chan188ebf52015-12-23 00:15:11 -0800872
873 // Although GroupDescriptions are not necessary for adding buckets to
874 // existing groups, we use one in the GroupChainElem. When the latter is
875 // processed, the info will be extracted for the bucketAdd call to groupService
876 GroupDescription l3ecmpGroupDesc =
877 new DefaultGroupDescription(
878 deviceId,
879 GroupDescription.Type.SELECT,
880 new GroupBuckets(Collections.singletonList(sbucket)),
881 l3ecmpGroupKey,
882 l3ecmpGroupId,
883 nextObjective.appId());
884 GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc, 1, true);
885
886 // update original NextGroup with new bucket-chain
887 // don't need to update pendingNextObjectives -- group already exists
888 Deque<GroupKey> newBucketChain = allGroupKeys.get(0);
889 newBucketChain.addFirst(l3ecmpGroupKey);
Charles Chan361154b2016-03-24 10:23:39 -0700890 List<Deque<GroupKey>> allOriginalKeys = Ofdpa2Pipeline.appKryo.deserialize(next.data());
Charles Chan188ebf52015-12-23 00:15:11 -0800891 allOriginalKeys.add(newBucketChain);
892 flowObjectiveStore.putNextGroup(nextObjective.id(),
893 new OfdpaNextGroup(allOriginalKeys, nextObjective));
894
895 log.debug("Adding to L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
896 deviceId, Integer.toHexString(l3ecmpGroupId),
897 l3ecmpGroupKey, nextObjective.id());
898 // send the innermost group
899 log.debug("Sending innermost group {} in group chain on device {} ",
Charles Chan5b9df8d2016-03-28 22:21:40 -0700900 Integer.toHexString(gi.innerMostGroupDesc.givenGroupId()), deviceId);
901 updatePendingGroups(gi.nextGroupDesc.appCookie(), l3ecmpGce);
902 groupService.addGroup(gi.innerMostGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800903
904 }
905
906 /**
907 * Removes the bucket in the top level group of a possible group-chain. Does
908 * not remove the groups in a group-chain pointed to by this bucket, as they
909 * may be in use (referenced by other groups) elsewhere.
910 *
911 * @param nextObjective the next group to remove a bucket from
912 * @param next the representation of the existing group-chain for this next objective
913 */
914 protected void removeBucketFromGroup(NextObjective nextObjective, NextGroup next) {
915 if (nextObjective.type() != NextObjective.Type.HASHED) {
916 log.warn("RemoveBuckets not applied to nextType:{} in dev:{} for next:{}",
917 nextObjective.type(), deviceId, nextObjective.id());
918 return;
919 }
920 Collection<TrafficTreatment> treatments = nextObjective.next();
921 TrafficTreatment treatment = treatments.iterator().next();
922 // find the bucket to remove by noting the outport, and figuring out the
923 // top-level group in the group-chain that indirectly references the port
924 PortNumber outport = null;
925 for (Instruction ins : treatment.allInstructions()) {
926 if (ins instanceof Instructions.OutputInstruction) {
927 outport = ((Instructions.OutputInstruction) ins).port();
928 break;
929 }
930 }
931 if (outport == null) {
932 log.error("next objective {} has no outport", nextObjective.id());
933 return;
934 }
935
Charles Chan361154b2016-03-24 10:23:39 -0700936 List<Deque<GroupKey>> allgkeys = Ofdpa2Pipeline.appKryo.deserialize(next.data());
Charles Chan188ebf52015-12-23 00:15:11 -0800937 Deque<GroupKey> foundChain = null;
938 int index = 0;
939 for (Deque<GroupKey> gkeys : allgkeys) {
940 GroupKey groupWithPort = gkeys.peekLast();
941 Group group = groupService.getGroup(deviceId, groupWithPort);
942 if (group == null) {
943 log.warn("Inconsistent group chain");
944 continue;
945 }
946 // last group in group chain should have a single bucket pointing to port
947 List<Instruction> lastIns = group.buckets().buckets().iterator()
948 .next().treatment().allInstructions();
949 for (Instruction i : lastIns) {
950 if (i instanceof Instructions.OutputInstruction) {
951 PortNumber lastport = ((Instructions.OutputInstruction) i).port();
952 if (lastport.equals(outport)) {
953 foundChain = gkeys;
954 break;
955 }
956 }
957 }
958 if (foundChain != null) {
959 break;
960 }
961 index++;
962 }
963 if (foundChain != null) {
964 //first groupkey is the one we want to modify
965 GroupKey modGroupKey = foundChain.peekFirst();
966 Group modGroup = groupService.getGroup(deviceId, modGroupKey);
967 //second groupkey is the one we wish to remove the reference to
968 GroupKey pointedGroupKey = null;
969 int i = 0;
970 for (GroupKey gk : foundChain) {
971 if (i++ == 1) {
972 pointedGroupKey = gk;
973 break;
974 }
975 }
976 Group pointedGroup = groupService.getGroup(deviceId, pointedGroupKey);
977 GroupBucket bucket = DefaultGroupBucket.createSelectGroupBucket(
978 DefaultTrafficTreatment.builder()
979 .group(pointedGroup.id())
980 .build());
981 GroupBuckets removeBuckets = new GroupBuckets(Collections
982 .singletonList(bucket));
983 log.debug("Removing buckets from group id {} for next id {} in device {}",
984 modGroup.id(), nextObjective.id(), deviceId);
985 groupService.removeBucketsFromGroup(deviceId, modGroupKey,
986 removeBuckets, modGroupKey,
987 nextObjective.appId());
988 //update store
989 allgkeys.remove(index);
990 flowObjectiveStore.putNextGroup(nextObjective.id(),
991 new OfdpaNextGroup(allgkeys, nextObjective));
992 } else {
993 log.warn("Could not find appropriate group-chain for removing bucket"
994 + " for next id {} in dev:{}", nextObjective.id(), deviceId);
995 }
996 }
997
998 /**
999 * Removes all groups in multiple possible group-chains that represent the next
1000 * objective.
1001 *
1002 * @param nextObjective the next objective to remove
1003 * @param next the NextGroup that represents the existing group-chain for
1004 * this next objective
1005 */
1006 protected void removeGroup(NextObjective nextObjective, NextGroup next) {
Charles Chan361154b2016-03-24 10:23:39 -07001007 List<Deque<GroupKey>> allgkeys = Ofdpa2Pipeline.appKryo.deserialize(next.data());
Charles Chan188ebf52015-12-23 00:15:11 -08001008 allgkeys.forEach(groupChain -> groupChain.forEach(groupKey ->
1009 groupService.removeGroup(deviceId, groupKey, nextObjective.appId())));
1010 flowObjectiveStore.removeNextGroup(nextObjective.id());
1011 }
1012
Saurav Das8be4e3a2016-03-11 17:19:07 -08001013 //////////////////////////////////////
1014 // Helper Methods and Classes
1015 //////////////////////////////////////
1016
1017 private void updatePendingNextObjective(GroupKey key, OfdpaNextGroup value) {
1018 List<OfdpaNextGroup> nextList = new CopyOnWriteArrayList<OfdpaNextGroup>();
1019 nextList.add(value);
1020 List<OfdpaNextGroup> ret = pendingNextObjectives.asMap()
1021 .putIfAbsent(key, nextList);
1022 if (ret != null) {
1023 ret.add(value);
1024 }
1025 }
1026
1027 private void updatePendingGroups(GroupKey gkey, GroupChainElem gce) {
1028 Set<GroupChainElem> gceSet = Collections.newSetFromMap(
1029 new ConcurrentHashMap<GroupChainElem, Boolean>());
1030 gceSet.add(gce);
1031 Set<GroupChainElem> retval = pendingGroups.putIfAbsent(gkey, gceSet);
1032 if (retval != null) {
1033 retval.add(gce);
1034 }
1035 }
1036
Charles Chan188ebf52015-12-23 00:15:11 -08001037 /**
1038 * Processes next element of a group chain. Assumption is that if this
1039 * group points to another group, the latter has already been created
1040 * and this driver has received notification for it. A second assumption is
1041 * that if there is another group waiting for this group then the appropriate
1042 * stores already have the information to act upon the notification for the
1043 * creation of this group.
1044 * <p>
1045 * The processing of the GroupChainElement depends on the number of groups
1046 * this element is waiting on. For all group types other than SIMPLE, a
1047 * GroupChainElement could be waiting on multiple groups.
1048 *
1049 * @param gce the group chain element to be processed next
1050 */
1051 private void processGroupChain(GroupChainElem gce) {
1052 int waitOnGroups = gce.decrementAndGetGroupsWaitedOn();
1053 if (waitOnGroups != 0) {
1054 log.debug("GCE: {} not ready to be processed", gce);
1055 return;
1056 }
1057 log.debug("GCE: {} ready to be processed", gce);
1058 if (gce.addBucketToGroup) {
1059 groupService.addBucketsToGroup(gce.groupDescription.deviceId(),
1060 gce.groupDescription.appCookie(),
1061 gce.groupDescription.buckets(),
1062 gce.groupDescription.appCookie(),
1063 gce.groupDescription.appId());
1064 } else {
1065 groupService.addGroup(gce.groupDescription);
1066 }
1067 }
1068
1069 private class GroupChecker implements Runnable {
1070 @Override
1071 public void run() {
1072 Set<GroupKey> keys = pendingGroups.keySet().stream()
1073 .filter(key -> groupService.getGroup(deviceId, key) != null)
1074 .collect(Collectors.toSet());
1075 Set<GroupKey> otherkeys = pendingNextObjectives.asMap().keySet().stream()
1076 .filter(otherkey -> groupService.getGroup(deviceId, otherkey) != null)
1077 .collect(Collectors.toSet());
1078 keys.addAll(otherkeys);
1079
1080 keys.stream().forEach(key ->
1081 processPendingGroupsOrNextObjectives(key, false));
1082 }
1083 }
1084
Saurav Das8be4e3a2016-03-11 17:19:07 -08001085 private class InnerGroupListener implements GroupListener {
1086 @Override
1087 public void event(GroupEvent event) {
1088 log.trace("received group event of type {}", event.type());
1089 if (event.type() == GroupEvent.Type.GROUP_ADDED) {
1090 GroupKey key = event.subject().appCookie();
1091 processPendingGroupsOrNextObjectives(key, true);
1092 }
1093 }
1094 }
1095
Charles Chan188ebf52015-12-23 00:15:11 -08001096 private void processPendingGroupsOrNextObjectives(GroupKey key, boolean added) {
1097 //first check for group chain
1098 Set<GroupChainElem> gceSet = pendingGroups.remove(key);
1099 if (gceSet != null) {
1100 for (GroupChainElem gce : gceSet) {
1101 log.info("Group service {} group key {} in device {}. "
Saurav Das0fd79d92016-03-07 10:58:36 -08001102 + "Processing next group in group chain with group id 0x{}",
Charles Chan188ebf52015-12-23 00:15:11 -08001103 (added) ? "ADDED" : "processed",
1104 key, deviceId,
1105 Integer.toHexString(gce.groupDescription.givenGroupId()));
1106 processGroupChain(gce);
1107 }
1108 } else {
1109 // otherwise chain complete - check for waiting nextObjectives
1110 List<OfdpaNextGroup> nextGrpList = pendingNextObjectives.getIfPresent(key);
1111 if (nextGrpList != null) {
1112 pendingNextObjectives.invalidate(key);
1113 nextGrpList.forEach(nextGrp -> {
1114 log.info("Group service {} group key {} in device:{}. "
Saurav Das0fd79d92016-03-07 10:58:36 -08001115 + "Done implementing next objective: {} <<-->> gid:0x{}",
Charles Chan188ebf52015-12-23 00:15:11 -08001116 (added) ? "ADDED" : "processed",
1117 key, deviceId, nextGrp.nextObjective().id(),
1118 Integer.toHexString(groupService.getGroup(deviceId, key)
1119 .givenGroupId()));
Charles Chan361154b2016-03-24 10:23:39 -07001120 Ofdpa2Pipeline.pass(nextGrp.nextObjective());
Charles Chan188ebf52015-12-23 00:15:11 -08001121 flowObjectiveStore.putNextGroup(nextGrp.nextObjective().id(), nextGrp);
1122 // check if addBuckets waiting for this completion
1123 NextObjective pendBkt = pendingBuckets
1124 .remove(nextGrp.nextObjective().id());
1125 if (pendBkt != null) {
1126 addBucketToGroup(pendBkt, nextGrp);
1127 }
1128 });
1129 }
1130 }
1131 }
1132
Saurav Das8be4e3a2016-03-11 17:19:07 -08001133 private int getNextAvailableIndex() {
1134 return (int) nextIndex.incrementAndGet();
1135 }
1136
Charles Chane849c192016-01-11 18:28:54 -08001137 /**
1138 * Returns a hash as the L2 Interface Group Key.
1139 *
1140 * Keep the lower 6-bit for port since port number usually smaller than 64.
1141 * Hash other information into remaining 28 bits.
1142 *
1143 * @param deviceId Device ID
1144 * @param vlanId VLAN ID
1145 * @param portNumber Port number
1146 * @return L2 interface group key
1147 */
1148 private int l2InterfaceGroupKey(
1149 DeviceId deviceId, VlanId vlanId, long portNumber) {
1150 int portLowerBits = (int) portNumber & PORT_LOWER_BITS_MASK;
1151 long portHigherBits = portNumber & PORT_HIGHER_BITS_MASK;
Charles Chand0fd5dc2016-02-16 23:14:49 -08001152 int hash = Objects.hash(deviceId, vlanId, portHigherBits);
Charles Chane849c192016-01-11 18:28:54 -08001153 return L2_INTERFACE_TYPE | (TYPE_MASK & hash << 6) | portLowerBits;
1154 }
1155
Charles Chan188ebf52015-12-23 00:15:11 -08001156 /**
1157 * Utility class for moving group information around.
1158 */
1159 private class GroupInfo {
Charles Chan5b9df8d2016-03-28 22:21:40 -07001160 /**
1161 * Description of the inner-most group of the group chain.
1162 * It is always an L2 interface group.
1163 */
1164 private GroupDescription innerMostGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -08001165
Charles Chan5b9df8d2016-03-28 22:21:40 -07001166 /**
1167 * Description of the next group in the group chain.
1168 * It can be L2 interface, L3 interface, L3 unicast, L3 VPN group.
1169 * It is possible that nextGroup is the same as the innerMostGroup.
1170 */
1171 private GroupDescription nextGroupDesc;
1172
1173 GroupInfo(GroupDescription innerMostGroupDesc, GroupDescription nextGroupDesc) {
1174 this.innerMostGroupDesc = innerMostGroupDesc;
1175 this.nextGroupDesc = nextGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -08001176 }
1177 }
1178
1179 /**
1180 * Represents an entire group-chain that implements a Next-Objective from
1181 * the application. The objective is represented as a list of deques, where
1182 * each deque is a separate chain of groups.
1183 * <p>
1184 * For example, an ECMP group with 3 buckets, where each bucket points to
1185 * a group chain of L3 Unicast and L2 interface groups will look like this:
1186 * <ul>
1187 * <li>List[0] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1188 * <li>List[1] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1189 * <li>List[2] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1190 * </ul>
1191 * where the first element of each deque is the same, representing the
1192 * top level ECMP group, while every other element represents a unique groupKey.
1193 * <p>
1194 * Also includes information about the next objective that
1195 * resulted in this group-chain.
1196 *
1197 */
1198 protected class OfdpaNextGroup implements NextGroup {
1199 private final NextObjective nextObj;
1200 private final List<Deque<GroupKey>> gkeys;
1201
1202 public OfdpaNextGroup(List<Deque<GroupKey>> gkeys, NextObjective nextObj) {
1203 this.gkeys = gkeys;
1204 this.nextObj = nextObj;
1205 }
1206
Charles Chan188ebf52015-12-23 00:15:11 -08001207 public List<Deque<GroupKey>> groupKey() {
1208 return gkeys;
1209 }
1210
1211 public NextObjective nextObjective() {
1212 return nextObj;
1213 }
1214
1215 @Override
1216 public byte[] data() {
Charles Chan361154b2016-03-24 10:23:39 -07001217 return Ofdpa2Pipeline.appKryo.serialize(gkeys);
Charles Chan188ebf52015-12-23 00:15:11 -08001218 }
1219 }
1220
1221 /**
1222 * Represents a group element that is part of a chain of groups.
1223 * Stores enough information to create a Group Description to add the group
1224 * to the switch by requesting the Group Service. Objects instantiating this
1225 * class are meant to be temporary and live as long as it is needed to wait for
1226 * preceding groups in the group chain to be created.
1227 */
1228 private class GroupChainElem {
1229 private GroupDescription groupDescription;
1230 private AtomicInteger waitOnGroups;
1231 private boolean addBucketToGroup;
1232
1233 GroupChainElem(GroupDescription groupDescription, int waitOnGroups,
1234 boolean addBucketToGroup) {
1235 this.groupDescription = groupDescription;
1236 this.waitOnGroups = new AtomicInteger(waitOnGroups);
1237 this.addBucketToGroup = addBucketToGroup;
1238 }
1239
1240 /**
1241 * This methods atomically decrements the counter for the number of
1242 * groups this GroupChainElement is waiting on, for notifications from
1243 * the Group Service. When this method returns a value of 0, this
1244 * GroupChainElement is ready to be processed.
1245 *
1246 * @return integer indication of the number of notifications being waited on
1247 */
1248 int decrementAndGetGroupsWaitedOn() {
1249 return waitOnGroups.decrementAndGet();
1250 }
1251
1252 @Override
1253 public String toString() {
1254 return (Integer.toHexString(groupDescription.givenGroupId()) +
1255 " groupKey: " + groupDescription.appCookie() +
1256 " waiting-on-groups: " + waitOnGroups.get() +
1257 " addBucketToGroup: " + addBucketToGroup +
1258 " device: " + deviceId);
1259 }
1260 }
1261}