blob: 80d83326ecfa6efbd88494438b6c6fc26b25afc5 [file] [log] [blame]
Brian O'Connor7cbbbb72016-04-09 02:13:23 -07001/*
2 * Copyright 2016-present Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Charles Chan188ebf52015-12-23 00:15:11 -080016package org.onosproject.driver.pipeline;
17
18import com.google.common.cache.Cache;
19import com.google.common.cache.CacheBuilder;
20import com.google.common.cache.RemovalCause;
21import com.google.common.cache.RemovalNotification;
Charles Chan5b9df8d2016-03-28 22:21:40 -070022import com.google.common.collect.ImmutableList;
23import com.google.common.collect.Lists;
Charles Chan188ebf52015-12-23 00:15:11 -080024import org.onlab.osgi.ServiceDirectory;
Charles Chan5b9df8d2016-03-28 22:21:40 -070025import org.onlab.packet.IpPrefix;
Charles Chan5270ed02016-01-30 23:22:37 -080026import org.onlab.packet.MacAddress;
Charles Chan188ebf52015-12-23 00:15:11 -080027import org.onlab.packet.MplsLabel;
28import org.onlab.packet.VlanId;
29import org.onosproject.core.ApplicationId;
30import org.onosproject.core.DefaultGroupId;
Charles Chan32562522016-04-07 14:37:14 -070031import org.onosproject.driver.extensions.OfdpaSetVlanVid;
Charles Chan188ebf52015-12-23 00:15:11 -080032import org.onosproject.net.DeviceId;
33import org.onosproject.net.PortNumber;
34import org.onosproject.net.behaviour.NextGroup;
35import org.onosproject.net.behaviour.PipelinerContext;
36import org.onosproject.net.flow.DefaultTrafficTreatment;
37import org.onosproject.net.flow.TrafficSelector;
38import org.onosproject.net.flow.TrafficTreatment;
39import org.onosproject.net.flow.criteria.Criterion;
40import org.onosproject.net.flow.criteria.VlanIdCriterion;
41import org.onosproject.net.flow.instructions.Instruction;
42import org.onosproject.net.flow.instructions.Instructions;
43import org.onosproject.net.flow.instructions.L2ModificationInstruction;
44import org.onosproject.net.flowobjective.FlowObjectiveStore;
45import org.onosproject.net.flowobjective.NextObjective;
46import org.onosproject.net.flowobjective.ObjectiveError;
47import org.onosproject.net.group.DefaultGroupBucket;
48import org.onosproject.net.group.DefaultGroupDescription;
49import org.onosproject.net.group.DefaultGroupKey;
50import org.onosproject.net.group.Group;
51import org.onosproject.net.group.GroupBucket;
52import org.onosproject.net.group.GroupBuckets;
53import org.onosproject.net.group.GroupDescription;
54import org.onosproject.net.group.GroupEvent;
55import org.onosproject.net.group.GroupKey;
56import org.onosproject.net.group.GroupListener;
57import org.onosproject.net.group.GroupService;
Saurav Das8be4e3a2016-03-11 17:19:07 -080058import org.onosproject.store.service.AtomicCounter;
59import org.onosproject.store.service.StorageService;
Charles Chan188ebf52015-12-23 00:15:11 -080060import org.slf4j.Logger;
61
62import java.util.ArrayDeque;
63import java.util.ArrayList;
64import java.util.Collection;
65import java.util.Collections;
66import java.util.Deque;
67import java.util.List;
Charles Chand0fd5dc2016-02-16 23:14:49 -080068import java.util.Objects;
Charles Chan188ebf52015-12-23 00:15:11 -080069import java.util.Set;
70import java.util.concurrent.ConcurrentHashMap;
71import java.util.concurrent.CopyOnWriteArrayList;
72import java.util.concurrent.Executors;
73import java.util.concurrent.ScheduledExecutorService;
74import java.util.concurrent.TimeUnit;
75import java.util.concurrent.atomic.AtomicInteger;
76import java.util.stream.Collectors;
77
78import static org.onlab.util.Tools.groupedThreads;
79import static org.slf4j.LoggerFactory.getLogger;
80
81/**
82 * Group handler for OFDPA2 pipeline.
83 */
Charles Chan361154b2016-03-24 10:23:39 -070084public class Ofdpa2GroupHandler {
Charles Chan188ebf52015-12-23 00:15:11 -080085 /*
86 * OFDPA requires group-id's to have a certain form.
87 * L2 Interface Groups have <4bits-0><12bits-vlanid><16bits-portid>
88 * L3 Unicast Groups have <4bits-2><28bits-index>
89 * MPLS Interface Groups have <4bits-9><4bits:0><24bits-index>
90 * L3 ECMP Groups have <4bits-7><28bits-index>
91 * L2 Flood Groups have <4bits-4><12bits-vlanid><16bits-index>
92 * L3 VPN Groups have <4bits-9><4bits-2><24bits-index>
93 */
Charles Chan425854b2016-04-11 15:32:12 -070094 protected static final int L2_INTERFACE_TYPE = 0x00000000;
95 protected static final int L3_INTERFACE_TYPE = 0x50000000;
96 protected static final int L3_UNICAST_TYPE = 0x20000000;
97 protected static final int L3_MULTICAST_TYPE = 0x60000000;
98 protected static final int MPLS_INTERFACE_TYPE = 0x90000000;
99 protected static final int MPLS_L3VPN_SUBTYPE = 0x92000000;
100 protected static final int L3_ECMP_TYPE = 0x70000000;
101 protected static final int L2_FLOOD_TYPE = 0x40000000;
Charles Chane849c192016-01-11 18:28:54 -0800102
Charles Chan425854b2016-04-11 15:32:12 -0700103 protected static final int TYPE_MASK = 0x0fffffff;
104 protected static final int SUBTYPE_MASK = 0x00ffffff;
105 protected static final int TYPE_VLAN_MASK = 0x0000ffff;
Charles Chane849c192016-01-11 18:28:54 -0800106
Charles Chan425854b2016-04-11 15:32:12 -0700107 protected static final int PORT_LOWER_BITS_MASK = 0x3f;
108 protected static final long PORT_HIGHER_BITS_MASK = ~PORT_LOWER_BITS_MASK;
Charles Chan188ebf52015-12-23 00:15:11 -0800109
110 private final Logger log = getLogger(getClass());
111 private ServiceDirectory serviceDirectory;
112 protected GroupService groupService;
Saurav Das8be4e3a2016-03-11 17:19:07 -0800113 protected StorageService storageService;
Charles Chan188ebf52015-12-23 00:15:11 -0800114
Charles Chan425854b2016-04-11 15:32:12 -0700115 protected DeviceId deviceId;
Charles Chan188ebf52015-12-23 00:15:11 -0800116 private FlowObjectiveStore flowObjectiveStore;
Charles Chanfc5c7802016-05-17 13:13:55 -0700117 private Cache<GroupKey, List<OfdpaNextGroup>> pendingAddNextObjectives;
118 private Cache<NextObjective, List<GroupKey>> pendingRemoveNextObjectives;
Charles Chan188ebf52015-12-23 00:15:11 -0800119 private ConcurrentHashMap<GroupKey, Set<GroupChainElem>> pendingGroups;
120 private ScheduledExecutorService groupChecker =
HIGUCHI Yutad9e01052016-04-14 09:31:42 -0700121 Executors.newScheduledThreadPool(2, groupedThreads("onos/pipeliner", "ofdpa2-%d", log));
Charles Chan188ebf52015-12-23 00:15:11 -0800122
123 // index number for group creation
Saurav Das8be4e3a2016-03-11 17:19:07 -0800124 private AtomicCounter nextIndex;
Charles Chan188ebf52015-12-23 00:15:11 -0800125
Charles Chan188ebf52015-12-23 00:15:11 -0800126 // local store for pending bucketAdds - by design there can only be one
127 // pending bucket for a group
128 protected ConcurrentHashMap<Integer, NextObjective> pendingBuckets = new ConcurrentHashMap<>();
129
130 protected void init(DeviceId deviceId, PipelinerContext context) {
131 this.deviceId = deviceId;
132 this.flowObjectiveStore = context.store();
133 this.serviceDirectory = context.directory();
134 this.groupService = serviceDirectory.get(GroupService.class);
Saurav Das8be4e3a2016-03-11 17:19:07 -0800135 this.storageService = serviceDirectory.get(StorageService.class);
Madan Jampanid5714e02016-04-19 14:15:20 -0700136 this.nextIndex = storageService.getAtomicCounter("group-id-index-counter");
Charles Chan188ebf52015-12-23 00:15:11 -0800137
Charles Chanfc5c7802016-05-17 13:13:55 -0700138 pendingAddNextObjectives = CacheBuilder.newBuilder()
Charles Chan188ebf52015-12-23 00:15:11 -0800139 .expireAfterWrite(20, TimeUnit.SECONDS)
140 .removalListener((
141 RemovalNotification<GroupKey, List<OfdpaNextGroup>> notification) -> {
142 if (notification.getCause() == RemovalCause.EXPIRED) {
143 notification.getValue().forEach(ofdpaNextGrp ->
Charles Chan361154b2016-03-24 10:23:39 -0700144 Ofdpa2Pipeline.fail(ofdpaNextGrp.nextObj,
Charles Chan188ebf52015-12-23 00:15:11 -0800145 ObjectiveError.GROUPINSTALLATIONFAILED));
Charles Chanfc5c7802016-05-17 13:13:55 -0700146 }
147 }).build();
Charles Chan188ebf52015-12-23 00:15:11 -0800148
Charles Chanfc5c7802016-05-17 13:13:55 -0700149 pendingRemoveNextObjectives = CacheBuilder.newBuilder()
150 .expireAfterWrite(20, TimeUnit.SECONDS)
151 .removalListener((
152 RemovalNotification<NextObjective, List<GroupKey>> notification) -> {
153 if (notification.getCause() == RemovalCause.EXPIRED) {
154 Ofdpa2Pipeline.fail(notification.getKey(),
155 ObjectiveError.GROUPREMOVALFAILED);
Charles Chan188ebf52015-12-23 00:15:11 -0800156 }
157 }).build();
158 pendingGroups = new ConcurrentHashMap<>();
159 groupChecker.scheduleAtFixedRate(new GroupChecker(), 0, 500, TimeUnit.MILLISECONDS);
160
161 groupService.addListener(new InnerGroupListener());
162 }
163
Saurav Das8be4e3a2016-03-11 17:19:07 -0800164 //////////////////////////////////////
165 // Group Creation
166 //////////////////////////////////////
167
Charles Chan188ebf52015-12-23 00:15:11 -0800168 protected void addGroup(NextObjective nextObjective) {
169 switch (nextObjective.type()) {
170 case SIMPLE:
171 Collection<TrafficTreatment> treatments = nextObjective.next();
172 if (treatments.size() != 1) {
173 log.error("Next Objectives of type Simple should only have a "
174 + "single Traffic Treatment. Next Objective Id:{}",
175 nextObjective.id());
Charles Chan361154b2016-03-24 10:23:39 -0700176 Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.BADPARAMS);
Charles Chan188ebf52015-12-23 00:15:11 -0800177 return;
178 }
179 processSimpleNextObjective(nextObjective);
180 break;
181 case BROADCAST:
182 processBroadcastNextObjective(nextObjective);
183 break;
184 case HASHED:
185 processHashedNextObjective(nextObjective);
186 break;
187 case FAILOVER:
Charles Chan361154b2016-03-24 10:23:39 -0700188 Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.UNSUPPORTED);
Charles Chan188ebf52015-12-23 00:15:11 -0800189 log.warn("Unsupported next objective type {}", nextObjective.type());
190 break;
191 default:
Charles Chan361154b2016-03-24 10:23:39 -0700192 Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.UNKNOWN);
Charles Chan188ebf52015-12-23 00:15:11 -0800193 log.warn("Unknown next objective type {}", nextObjective.type());
194 }
195 }
196
197 /**
198 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
199 * a chain of groups. The simple Next Objective passed
200 * in by the application has to be broken up into a group chain
201 * comprising of an L3 Unicast Group that points to an L2 Interface
202 * Group which in-turn points to an output port. In some cases, the simple
203 * next Objective can just be an L2 interface without the need for chaining.
204 *
205 * @param nextObj the nextObjective of type SIMPLE
206 */
207 private void processSimpleNextObjective(NextObjective nextObj) {
208 TrafficTreatment treatment = nextObj.next().iterator().next();
209 // determine if plain L2 or L3->L2
210 boolean plainL2 = true;
211 for (Instruction ins : treatment.allInstructions()) {
212 if (ins.type() == Instruction.Type.L2MODIFICATION) {
213 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
214 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.ETH_DST ||
215 l2ins.subtype() == L2ModificationInstruction.L2SubType.ETH_SRC) {
216 plainL2 = false;
217 break;
218 }
219 }
220 }
221
222 if (plainL2) {
223 createL2InterfaceGroup(nextObj);
224 return;
225 }
226
227 // break up simple next objective to GroupChain objects
228 GroupInfo groupInfo = createL2L3Chain(treatment, nextObj.id(),
Saurav Das8be4e3a2016-03-11 17:19:07 -0800229 nextObj.appId(), false,
230 nextObj.meta());
Charles Chan188ebf52015-12-23 00:15:11 -0800231 if (groupInfo == null) {
232 log.error("Could not process nextObj={} in dev:{}", nextObj.id(), deviceId);
233 return;
234 }
235 // create object for local and distributed storage
236 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700237 gkeyChain.addFirst(groupInfo.innerMostGroupDesc.appCookie());
238 gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
Charles Chan188ebf52015-12-23 00:15:11 -0800239 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(
240 Collections.singletonList(gkeyChain),
241 nextObj);
242
243 // store l3groupkey with the ofdpaNextGroup for the nextObjective that depends on it
Charles Chan5b9df8d2016-03-28 22:21:40 -0700244 updatePendingNextObjective(groupInfo.nextGroupDesc.appCookie(), ofdpaGrp);
Charles Chan188ebf52015-12-23 00:15:11 -0800245
246 // now we are ready to send the l2 groupDescription (inner), as all the stores
247 // that will get async replies have been updated. By waiting to update
248 // the stores, we prevent nasty race conditions.
Charles Chan5b9df8d2016-03-28 22:21:40 -0700249 groupService.addGroup(groupInfo.innerMostGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800250 }
251
Charles Chan188ebf52015-12-23 00:15:11 -0800252 /**
253 * Creates a simple L2 Interface Group.
254 *
255 * @param nextObj the next Objective
256 */
257 private void createL2InterfaceGroup(NextObjective nextObj) {
Charles Chan5b9df8d2016-03-28 22:21:40 -0700258 VlanId assignedVlan = Ofdpa2Pipeline.readVlanFromSelector(nextObj.meta());
259 if (assignedVlan == null) {
260 log.warn("VLAN ID required by simple next obj is missing. Abort.");
261 Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
Charles Chan188ebf52015-12-23 00:15:11 -0800262 return;
263 }
264
Charles Chan5b9df8d2016-03-28 22:21:40 -0700265 List<GroupInfo> groupInfos = prepareL2InterfaceGroup(nextObj, assignedVlan);
Charles Chan188ebf52015-12-23 00:15:11 -0800266
Charles Chan5b9df8d2016-03-28 22:21:40 -0700267 // There is only one L2 interface group in this case
268 GroupDescription l2InterfaceGroupDesc = groupInfos.get(0).innerMostGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -0800269
Charles Chan5b9df8d2016-03-28 22:21:40 -0700270 // Put all dependency information into allGroupKeys
271 List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
272 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
273 gkeyChain.addFirst(l2InterfaceGroupDesc.appCookie());
274 allGroupKeys.add(gkeyChain);
Charles Chan188ebf52015-12-23 00:15:11 -0800275
Charles Chan5b9df8d2016-03-28 22:21:40 -0700276 // Point the next objective to this group
277 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
278 updatePendingNextObjective(l2InterfaceGroupDesc.appCookie(), ofdpaGrp);
279
280 // Start installing the inner-most group
281 groupService.addGroup(l2InterfaceGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800282 }
283
284 /**
285 * Creates one of two possible group-chains from the treatment
286 * passed in. Depending on the MPLS boolean, this method either creates
Charles Chan425854b2016-04-11 15:32:12 -0700287 * an L3Unicast Group --&gt; L2Interface Group, if mpls is false;
288 * or MPLSInterface Group --&gt; L2Interface Group, if mpls is true;
Charles Chan188ebf52015-12-23 00:15:11 -0800289 * The returned 'inner' group description is always the L2 Interface group.
290 *
291 * @param treatment that needs to be broken up to create the group chain
292 * @param nextId of the next objective that needs this group chain
293 * @param appId of the application that sent this next objective
294 * @param mpls determines if L3Unicast or MPLSInterface group is created
295 * @param meta metadata passed in by the application as part of the nextObjective
296 * @return GroupInfo containing the GroupDescription of the
297 * L2Interface group(inner) and the GroupDescription of the (outer)
298 * L3Unicast/MPLSInterface group. May return null if there is an
299 * error in processing the chain
300 */
Charles Chan425854b2016-04-11 15:32:12 -0700301 protected GroupInfo createL2L3Chain(TrafficTreatment treatment, int nextId,
Saurav Das8be4e3a2016-03-11 17:19:07 -0800302 ApplicationId appId, boolean mpls,
303 TrafficSelector meta) {
Charles Chan188ebf52015-12-23 00:15:11 -0800304 // for the l2interface group, get vlan and port info
305 // for the outer group, get the src/dst mac, and vlan info
306 TrafficTreatment.Builder outerTtb = DefaultTrafficTreatment.builder();
307 TrafficTreatment.Builder innerTtb = DefaultTrafficTreatment.builder();
308 VlanId vlanid = null;
309 long portNum = 0;
310 boolean setVlan = false, popVlan = false;
Charles Chand0fd5dc2016-02-16 23:14:49 -0800311 MacAddress srcMac = MacAddress.ZERO;
Charles Chan5270ed02016-01-30 23:22:37 -0800312 MacAddress dstMac = MacAddress.ZERO;
Charles Chan188ebf52015-12-23 00:15:11 -0800313 for (Instruction ins : treatment.allInstructions()) {
314 if (ins.type() == Instruction.Type.L2MODIFICATION) {
315 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
316 switch (l2ins.subtype()) {
317 case ETH_DST:
Charles Chan5270ed02016-01-30 23:22:37 -0800318 dstMac = ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac();
319 outerTtb.setEthDst(dstMac);
Charles Chan188ebf52015-12-23 00:15:11 -0800320 break;
321 case ETH_SRC:
Charles Chand0fd5dc2016-02-16 23:14:49 -0800322 srcMac = ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac();
323 outerTtb.setEthSrc(srcMac);
Charles Chan188ebf52015-12-23 00:15:11 -0800324 break;
325 case VLAN_ID:
326 vlanid = ((L2ModificationInstruction.ModVlanIdInstruction) l2ins).vlanId();
Charles Chan32562522016-04-07 14:37:14 -0700327 OfdpaSetVlanVid ofdpaSetVlanVid = new OfdpaSetVlanVid(vlanid);
328 outerTtb.extension(ofdpaSetVlanVid, deviceId);
Charles Chan188ebf52015-12-23 00:15:11 -0800329 setVlan = true;
330 break;
331 case VLAN_POP:
332 innerTtb.popVlan();
333 popVlan = true;
334 break;
335 case DEC_MPLS_TTL:
336 case MPLS_LABEL:
337 case MPLS_POP:
338 case MPLS_PUSH:
339 case VLAN_PCP:
340 case VLAN_PUSH:
341 default:
342 break;
343 }
344 } else if (ins.type() == Instruction.Type.OUTPUT) {
345 portNum = ((Instructions.OutputInstruction) ins).port().toLong();
346 innerTtb.add(ins);
347 } else {
348 log.warn("Driver does not handle this type of TrafficTreatment"
349 + " instruction in nextObjectives: {}", ins.type());
350 }
351 }
352
353 if (vlanid == null && meta != null) {
354 // use metadata if available
355 Criterion vidCriterion = meta.getCriterion(Criterion.Type.VLAN_VID);
356 if (vidCriterion != null) {
357 vlanid = ((VlanIdCriterion) vidCriterion).vlanId();
358 }
359 // if vlan is not set, use the vlan in metadata for outerTtb
360 if (vlanid != null && !setVlan) {
Charles Chan32562522016-04-07 14:37:14 -0700361 OfdpaSetVlanVid ofdpaSetVlanVid = new OfdpaSetVlanVid(vlanid);
362 outerTtb.extension(ofdpaSetVlanVid, deviceId);
Charles Chan188ebf52015-12-23 00:15:11 -0800363 }
364 }
365
366 if (vlanid == null) {
367 log.error("Driver cannot process an L2/L3 group chain without "
368 + "egress vlan information for dev: {} port:{}",
369 deviceId, portNum);
370 return null;
371 }
372
373 if (!setVlan && !popVlan) {
374 // untagged outgoing port
375 TrafficTreatment.Builder temp = DefaultTrafficTreatment.builder();
376 temp.popVlan();
377 innerTtb.build().allInstructions().forEach(i -> temp.add(i));
378 innerTtb = temp;
379 }
380
381 // assemble information for ofdpa l2interface group
Charles Chane849c192016-01-11 18:28:54 -0800382 int l2groupId = L2_INTERFACE_TYPE | (vlanid.toShort() << 16) | (int) portNum;
Saurav Das8be4e3a2016-03-11 17:19:07 -0800383 // a globally unique groupkey that is different for ports in the same device,
Charles Chan188ebf52015-12-23 00:15:11 -0800384 // but different for the same portnumber on different devices. Also different
385 // for the various group-types created out of the same next objective.
Charles Chane849c192016-01-11 18:28:54 -0800386 int l2gk = l2InterfaceGroupKey(deviceId, vlanid, portNum);
Charles Chan361154b2016-03-24 10:23:39 -0700387 final GroupKey l2groupkey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l2gk));
Charles Chan188ebf52015-12-23 00:15:11 -0800388
389 // assemble information for outer group
390 GroupDescription outerGrpDesc = null;
391 if (mpls) {
392 // outer group is MPLSInteface
Saurav Das8be4e3a2016-03-11 17:19:07 -0800393 int mplsInterfaceIndex = getNextAvailableIndex();
394 int mplsgroupId = MPLS_INTERFACE_TYPE | (SUBTYPE_MASK & mplsInterfaceIndex);
395 final GroupKey mplsgroupkey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700396 Ofdpa2Pipeline.appKryo.serialize(mplsInterfaceIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800397 outerTtb.group(new DefaultGroupId(l2groupId));
398 // create the mpls-interface group description to wait for the
399 // l2 interface group to be processed
400 GroupBucket mplsinterfaceGroupBucket =
401 DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
402 outerGrpDesc = new DefaultGroupDescription(
403 deviceId,
404 GroupDescription.Type.INDIRECT,
405 new GroupBuckets(Collections.singletonList(
406 mplsinterfaceGroupBucket)),
407 mplsgroupkey,
408 mplsgroupId,
409 appId);
410 log.debug("Trying MPLS-Interface: device:{} gid:{} gkey:{} nextid:{}",
411 deviceId, Integer.toHexString(mplsgroupId),
412 mplsgroupkey, nextId);
413 } else {
414 // outer group is L3Unicast
Saurav Das8be4e3a2016-03-11 17:19:07 -0800415 int l3unicastIndex = getNextAvailableIndex();
416 int l3groupId = L3_UNICAST_TYPE | (TYPE_MASK & l3unicastIndex);
417 final GroupKey l3groupkey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700418 Ofdpa2Pipeline.appKryo.serialize(l3unicastIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800419 outerTtb.group(new DefaultGroupId(l2groupId));
420 // create the l3unicast group description to wait for the
421 // l2 interface group to be processed
422 GroupBucket l3unicastGroupBucket =
423 DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
424 outerGrpDesc = new DefaultGroupDescription(
425 deviceId,
426 GroupDescription.Type.INDIRECT,
427 new GroupBuckets(Collections.singletonList(
428 l3unicastGroupBucket)),
429 l3groupkey,
430 l3groupId,
431 appId);
432 log.debug("Trying L3Unicast: device:{} gid:{} gkey:{} nextid:{}",
433 deviceId, Integer.toHexString(l3groupId),
434 l3groupkey, nextId);
435 }
436
437 // store l2groupkey with the groupChainElem for the outer-group that depends on it
438 GroupChainElem gce = new GroupChainElem(outerGrpDesc, 1, false);
439 updatePendingGroups(l2groupkey, gce);
440
441 // create group description for the inner l2interfacegroup
Charles Chan5b9df8d2016-03-28 22:21:40 -0700442 GroupBucket l2InterfaceGroupBucket =
Charles Chan188ebf52015-12-23 00:15:11 -0800443 DefaultGroupBucket.createIndirectGroupBucket(innerTtb.build());
444 GroupDescription l2groupDescription =
445 new DefaultGroupDescription(
446 deviceId,
447 GroupDescription.Type.INDIRECT,
448 new GroupBuckets(Collections.singletonList(
Charles Chan5b9df8d2016-03-28 22:21:40 -0700449 l2InterfaceGroupBucket)),
Charles Chan188ebf52015-12-23 00:15:11 -0800450 l2groupkey,
451 l2groupId,
452 appId);
453 log.debug("Trying L2Interface: device:{} gid:{} gkey:{} nextId:{}",
454 deviceId, Integer.toHexString(l2groupId),
455 l2groupkey, nextId);
456 return new GroupInfo(l2groupDescription, outerGrpDesc);
457
458 }
459
460 /**
461 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
462 * a chain of groups. The broadcast Next Objective passed in by the application
463 * has to be broken up into a group chain comprising of an
464 * L2 Flood group whose buckets point to L2 Interface groups.
465 *
466 * @param nextObj the nextObjective of type BROADCAST
467 */
468 private void processBroadcastNextObjective(NextObjective nextObj) {
Charles Chan5b9df8d2016-03-28 22:21:40 -0700469 VlanId assignedVlan = Ofdpa2Pipeline.readVlanFromSelector(nextObj.meta());
470 if (assignedVlan == null) {
471 log.warn("VLAN ID required by broadcast next obj is missing. Abort.");
472 Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
Charles Chan188ebf52015-12-23 00:15:11 -0800473 return;
474 }
Charles Chan188ebf52015-12-23 00:15:11 -0800475
Charles Chan5b9df8d2016-03-28 22:21:40 -0700476 List<GroupInfo> groupInfos = prepareL2InterfaceGroup(nextObj, assignedVlan);
477
478 IpPrefix ipDst = Ofdpa2Pipeline.readIpDstFromSelector(nextObj.meta());
479 if (ipDst != null) {
480 if (ipDst.isMulticast()) {
481 createL3MulticastGroup(nextObj, assignedVlan, groupInfos);
482 } else {
483 log.warn("Broadcast NextObj with non-multicast IP address {}", nextObj);
484 Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
485 return;
486 }
487 } else {
488 createL2FloodGroup(nextObj, assignedVlan, groupInfos);
489 }
490 }
491
492 private List<GroupInfo> prepareL2InterfaceGroup(NextObjective nextObj, VlanId assignedVlan) {
493 ImmutableList.Builder<GroupInfo> groupInfoBuilder = ImmutableList.builder();
494
495 // break up broadcast next objective to multiple groups
496 Collection<TrafficTreatment> buckets = nextObj.next();
497
Charles Chan188ebf52015-12-23 00:15:11 -0800498 // each treatment is converted to an L2 interface group
Charles Chan188ebf52015-12-23 00:15:11 -0800499 for (TrafficTreatment treatment : buckets) {
500 TrafficTreatment.Builder newTreatment = DefaultTrafficTreatment.builder();
501 PortNumber portNum = null;
Charles Chan5b9df8d2016-03-28 22:21:40 -0700502 VlanId egressVlan = null;
Charles Chan188ebf52015-12-23 00:15:11 -0800503 // ensure that the only allowed treatments are pop-vlan and output
504 for (Instruction ins : treatment.allInstructions()) {
505 if (ins.type() == Instruction.Type.L2MODIFICATION) {
506 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
507 switch (l2ins.subtype()) {
508 case VLAN_POP:
509 newTreatment.add(l2ins);
510 break;
Charles Chan5b9df8d2016-03-28 22:21:40 -0700511 case VLAN_ID:
512 egressVlan = ((L2ModificationInstruction.ModVlanIdInstruction) l2ins).vlanId();
513 break;
Charles Chan188ebf52015-12-23 00:15:11 -0800514 default:
515 log.debug("action {} not permitted for broadcast nextObj",
516 l2ins.subtype());
517 break;
518 }
519 } else if (ins.type() == Instruction.Type.OUTPUT) {
520 portNum = ((Instructions.OutputInstruction) ins).port();
521 newTreatment.add(ins);
522 } else {
Charles Chane849c192016-01-11 18:28:54 -0800523 log.debug("TrafficTreatment of type {} not permitted in " +
524 " broadcast nextObjective", ins.type());
Charles Chan188ebf52015-12-23 00:15:11 -0800525 }
526 }
527
Charles Chan188ebf52015-12-23 00:15:11 -0800528 // assemble info for l2 interface group
Charles Chan5b9df8d2016-03-28 22:21:40 -0700529 VlanId l2InterfaceGroupVlan =
530 (egressVlan != null && !assignedVlan.equals(egressVlan)) ?
531 egressVlan : assignedVlan;
532 int l2gk = l2InterfaceGroupKey(deviceId, l2InterfaceGroupVlan, portNum.toLong());
533 final GroupKey l2InterfaceGroupKey =
534 new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l2gk));
535 int l2InterfaceGroupId = L2_INTERFACE_TYPE | (l2InterfaceGroupVlan.toShort() << 16) |
Charles Chan188ebf52015-12-23 00:15:11 -0800536 (int) portNum.toLong();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700537 GroupBucket l2InterfaceGroupBucket =
Charles Chan188ebf52015-12-23 00:15:11 -0800538 DefaultGroupBucket.createIndirectGroupBucket(newTreatment.build());
Charles Chan5b9df8d2016-03-28 22:21:40 -0700539 GroupDescription l2InterfaceGroupDescription =
Charles Chan188ebf52015-12-23 00:15:11 -0800540 new DefaultGroupDescription(
541 deviceId,
542 GroupDescription.Type.INDIRECT,
543 new GroupBuckets(Collections.singletonList(
Charles Chan5b9df8d2016-03-28 22:21:40 -0700544 l2InterfaceGroupBucket)),
545 l2InterfaceGroupKey,
546 l2InterfaceGroupId,
Charles Chan188ebf52015-12-23 00:15:11 -0800547 nextObj.appId());
548 log.debug("Trying L2-Interface: device:{} gid:{} gkey:{} nextid:{}",
Charles Chan5b9df8d2016-03-28 22:21:40 -0700549 deviceId, Integer.toHexString(l2InterfaceGroupId),
550 l2InterfaceGroupKey, nextObj.id());
Charles Chan188ebf52015-12-23 00:15:11 -0800551
Charles Chan5b9df8d2016-03-28 22:21:40 -0700552 groupInfoBuilder.add(new GroupInfo(l2InterfaceGroupDescription,
553 l2InterfaceGroupDescription));
Charles Chan188ebf52015-12-23 00:15:11 -0800554 }
Charles Chan5b9df8d2016-03-28 22:21:40 -0700555 return groupInfoBuilder.build();
556 }
Charles Chan188ebf52015-12-23 00:15:11 -0800557
Charles Chan5b9df8d2016-03-28 22:21:40 -0700558 private void createL2FloodGroup(NextObjective nextObj, VlanId vlanId, List<GroupInfo> groupInfos) {
Charles Chan188ebf52015-12-23 00:15:11 -0800559 // assemble info for l2 flood group
Saurav Das0fd79d92016-03-07 10:58:36 -0800560 // since there can be only one flood group for a vlan, its index is always the same - 0
561 Integer l2floodgroupId = L2_FLOOD_TYPE | (vlanId.toShort() << 16);
Saurav Das8be4e3a2016-03-11 17:19:07 -0800562 int l2floodgk = getNextAvailableIndex();
Charles Chan361154b2016-03-24 10:23:39 -0700563 final GroupKey l2floodgroupkey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l2floodgk));
Charles Chan5b9df8d2016-03-28 22:21:40 -0700564
Charles Chan188ebf52015-12-23 00:15:11 -0800565 // collection of group buckets pointing to all the l2 interface groups
Charles Chan5b9df8d2016-03-28 22:21:40 -0700566 List<GroupBucket> l2floodBuckets = Lists.newArrayList();
567 groupInfos.forEach(groupInfo -> {
568 GroupDescription l2intGrpDesc = groupInfo.nextGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -0800569 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
570 ttb.group(new DefaultGroupId(l2intGrpDesc.givenGroupId()));
571 GroupBucket abucket = DefaultGroupBucket.createAllGroupBucket(ttb.build());
572 l2floodBuckets.add(abucket);
Charles Chan5b9df8d2016-03-28 22:21:40 -0700573 });
Charles Chan188ebf52015-12-23 00:15:11 -0800574 // create the l2flood group-description to wait for all the
575 // l2interface groups to be processed
576 GroupDescription l2floodGroupDescription =
577 new DefaultGroupDescription(
578 deviceId,
579 GroupDescription.Type.ALL,
580 new GroupBuckets(l2floodBuckets),
581 l2floodgroupkey,
582 l2floodgroupId,
583 nextObj.appId());
Charles Chan188ebf52015-12-23 00:15:11 -0800584 log.debug("Trying L2-Flood: device:{} gid:{} gkey:{} nextid:{}",
585 deviceId, Integer.toHexString(l2floodgroupId),
586 l2floodgroupkey, nextObj.id());
587
Charles Chan5b9df8d2016-03-28 22:21:40 -0700588 // Put all dependency information into allGroupKeys
589 List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
590 groupInfos.forEach(groupInfo -> {
591 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
592 // In this case we should have L2 interface group only
593 gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
594 gkeyChain.addFirst(l2floodgroupkey);
595 allGroupKeys.add(gkeyChain);
596 });
Charles Chan188ebf52015-12-23 00:15:11 -0800597
Charles Chan5b9df8d2016-03-28 22:21:40 -0700598 // Point the next objective to this group
599 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
Charles Chan188ebf52015-12-23 00:15:11 -0800600 updatePendingNextObjective(l2floodgroupkey, ofdpaGrp);
601
Charles Chan5b9df8d2016-03-28 22:21:40 -0700602 GroupChainElem gce = new GroupChainElem(l2floodGroupDescription,
603 groupInfos.size(), false);
604 groupInfos.forEach(groupInfo -> {
605 // Point this group to the next group
606 updatePendingGroups(groupInfo.nextGroupDesc.appCookie(), gce);
607 // Start installing the inner-most group
608 groupService.addGroup(groupInfo.innerMostGroupDesc);
609 });
610 }
611
612 private void createL3MulticastGroup(NextObjective nextObj, VlanId vlanId, List<GroupInfo> groupInfos) {
613 List<GroupBucket> l3McastBuckets = new ArrayList<>();
614 groupInfos.forEach(groupInfo -> {
615 // Points to L3 interface group if there is one.
616 // Otherwise points to L2 interface group directly.
617 GroupDescription nextGroupDesc = (groupInfo.nextGroupDesc != null) ?
618 groupInfo.nextGroupDesc : groupInfo.innerMostGroupDesc;
619 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
620 ttb.group(new DefaultGroupId(nextGroupDesc.givenGroupId()));
621 GroupBucket abucket = DefaultGroupBucket.createAllGroupBucket(ttb.build());
622 l3McastBuckets.add(abucket);
623 });
624
625 int l3MulticastIndex = getNextAvailableIndex();
626 int l3MulticastGroupId = L3_MULTICAST_TYPE | vlanId.toShort() << 16 | (TYPE_VLAN_MASK & l3MulticastIndex);
627 final GroupKey l3MulticastGroupKey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l3MulticastIndex));
628
629 GroupDescription l3MulticastGroupDesc = new DefaultGroupDescription(deviceId,
630 GroupDescription.Type.ALL,
631 new GroupBuckets(l3McastBuckets),
632 l3MulticastGroupKey,
633 l3MulticastGroupId,
634 nextObj.appId());
635
636 // Put all dependency information into allGroupKeys
637 List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
638 groupInfos.forEach(groupInfo -> {
639 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
640 gkeyChain.addFirst(groupInfo.innerMostGroupDesc.appCookie());
641 // Add L3 interface group to the chain if there is one.
642 if (!groupInfo.nextGroupDesc.equals(groupInfo.innerMostGroupDesc)) {
643 gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
644 }
645 gkeyChain.addFirst(l3MulticastGroupKey);
646 allGroupKeys.add(gkeyChain);
647 });
648
649 // Point the next objective to this group
650 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
651 updatePendingNextObjective(l3MulticastGroupKey, ofdpaGrp);
652
653 GroupChainElem outerGce = new GroupChainElem(l3MulticastGroupDesc,
654 groupInfos.size(), false);
655 groupInfos.forEach(groupInfo -> {
656 // Point this group (L3 multicast) to the next group
657 updatePendingGroups(groupInfo.nextGroupDesc.appCookie(), outerGce);
658
659 // Point next group to inner-most group, if any
660 if (!groupInfo.nextGroupDesc.equals(groupInfo.innerMostGroupDesc)) {
661 GroupChainElem innerGce = new GroupChainElem(groupInfo.nextGroupDesc,
662 1, false);
663 updatePendingGroups(groupInfo.innerMostGroupDesc.appCookie(), innerGce);
664 }
665
666 // Start installing the inner-most group
667 groupService.addGroup(groupInfo.innerMostGroupDesc);
668 });
Charles Chan188ebf52015-12-23 00:15:11 -0800669 }
670
Charles Chan188ebf52015-12-23 00:15:11 -0800671 /**
672 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
673 * a chain of groups. The hashed Next Objective passed in by the application
674 * has to be broken up into a group chain comprising of an
675 * L3 ECMP group as the top level group. Buckets of this group can point
676 * to a variety of groups in a group chain, depending on the whether
677 * MPLS labels are being pushed or not.
678 * <p>
679 * NOTE: We do not create MPLS ECMP groups as they are unimplemented in
680 * OF-DPA 2.0 (even though it is in the spec). Therefore we do not
681 * check the nextObjective meta to see what is matching before being
682 * sent to this nextObjective.
683 *
684 * @param nextObj the nextObjective of type HASHED
685 */
686 private void processHashedNextObjective(NextObjective nextObj) {
687 // storage for all group keys in the chain of groups created
688 List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
689 List<GroupInfo> unsentGroups = new ArrayList<>();
690 createHashBucketChains(nextObj, allGroupKeys, unsentGroups);
691
692 // now we can create the outermost L3 ECMP group
693 List<GroupBucket> l3ecmpGroupBuckets = new ArrayList<>();
694 for (GroupInfo gi : unsentGroups) {
695 // create ECMP bucket to point to the outer group
696 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700697 ttb.group(new DefaultGroupId(gi.nextGroupDesc.givenGroupId()));
Charles Chan188ebf52015-12-23 00:15:11 -0800698 GroupBucket sbucket = DefaultGroupBucket
699 .createSelectGroupBucket(ttb.build());
700 l3ecmpGroupBuckets.add(sbucket);
701 }
Saurav Das8be4e3a2016-03-11 17:19:07 -0800702 int l3ecmpIndex = getNextAvailableIndex();
703 int l3ecmpGroupId = L3_ECMP_TYPE | (TYPE_MASK & l3ecmpIndex);
704 GroupKey l3ecmpGroupKey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700705 Ofdpa2Pipeline.appKryo.serialize(l3ecmpIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800706 GroupDescription l3ecmpGroupDesc =
707 new DefaultGroupDescription(
708 deviceId,
709 GroupDescription.Type.SELECT,
710 new GroupBuckets(l3ecmpGroupBuckets),
711 l3ecmpGroupKey,
712 l3ecmpGroupId,
713 nextObj.appId());
714 GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc,
715 l3ecmpGroupBuckets.size(),
716 false);
717
718 // create objects for local and distributed storage
719 allGroupKeys.forEach(gkeyChain -> gkeyChain.addFirst(l3ecmpGroupKey));
720 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
721
722 // store l3ecmpGroupKey with the ofdpaGroupChain for the nextObjective
723 // that depends on it
724 updatePendingNextObjective(l3ecmpGroupKey, ofdpaGrp);
725
726 log.debug("Trying L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
727 deviceId, Integer.toHexString(l3ecmpGroupId),
728 l3ecmpGroupKey, nextObj.id());
729 // finally we are ready to send the innermost groups
730 for (GroupInfo gi : unsentGroups) {
731 log.debug("Sending innermost group {} in group chain on device {} ",
Charles Chan5b9df8d2016-03-28 22:21:40 -0700732 Integer.toHexString(gi.innerMostGroupDesc.givenGroupId()), deviceId);
733 updatePendingGroups(gi.nextGroupDesc.appCookie(), l3ecmpGce);
734 groupService.addGroup(gi.innerMostGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800735 }
736
737 }
738
739 /**
740 * Creates group chains for all buckets in a hashed group, and stores the
741 * GroupInfos and GroupKeys for all the groups in the lists passed in, which
742 * should be empty.
743 * <p>
744 * Does not create the top level ECMP group. Does not actually send the
745 * groups to the groupService.
746 *
747 * @param nextObj the Next Objective with buckets that need to be converted
748 * to group chains
749 * @param allGroupKeys a list to store groupKey for each bucket-group-chain
750 * @param unsentGroups a list to store GroupInfo for each bucket-group-chain
751 */
752 private void createHashBucketChains(NextObjective nextObj,
Saurav Das8be4e3a2016-03-11 17:19:07 -0800753 List<Deque<GroupKey>> allGroupKeys,
754 List<GroupInfo> unsentGroups) {
Charles Chan188ebf52015-12-23 00:15:11 -0800755 // break up hashed next objective to multiple groups
756 Collection<TrafficTreatment> buckets = nextObj.next();
757
758 for (TrafficTreatment bucket : buckets) {
759 //figure out how many labels are pushed in each bucket
760 int labelsPushed = 0;
761 MplsLabel innermostLabel = null;
762 for (Instruction ins : bucket.allInstructions()) {
763 if (ins.type() == Instruction.Type.L2MODIFICATION) {
764 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
765 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.MPLS_PUSH) {
766 labelsPushed++;
767 }
768 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.MPLS_LABEL) {
769 if (innermostLabel == null) {
Ray Milkey125572b2016-02-22 16:48:17 -0800770 innermostLabel = ((L2ModificationInstruction.ModMplsLabelInstruction) l2ins).label();
Charles Chan188ebf52015-12-23 00:15:11 -0800771 }
772 }
773 }
774 }
775
776 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
777 // XXX we only deal with 0 and 1 label push right now
778 if (labelsPushed == 0) {
779 GroupInfo nolabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
780 nextObj.appId(), false,
781 nextObj.meta());
782 if (nolabelGroupInfo == null) {
783 log.error("Could not process nextObj={} in dev:{}",
784 nextObj.id(), deviceId);
785 return;
786 }
Charles Chan5b9df8d2016-03-28 22:21:40 -0700787 gkeyChain.addFirst(nolabelGroupInfo.innerMostGroupDesc.appCookie());
788 gkeyChain.addFirst(nolabelGroupInfo.nextGroupDesc.appCookie());
Charles Chan188ebf52015-12-23 00:15:11 -0800789
790 // we can't send the inner group description yet, as we have to
791 // create the dependent ECMP group first. So we store..
792 unsentGroups.add(nolabelGroupInfo);
793
794 } else if (labelsPushed == 1) {
795 GroupInfo onelabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
796 nextObj.appId(), true,
797 nextObj.meta());
798 if (onelabelGroupInfo == null) {
799 log.error("Could not process nextObj={} in dev:{}",
800 nextObj.id(), deviceId);
801 return;
802 }
803 // we need to add another group to this chain - the L3VPN group
804 TrafficTreatment.Builder l3vpnTtb = DefaultTrafficTreatment.builder();
805 l3vpnTtb.pushMpls()
806 .setMpls(innermostLabel)
807 .setMplsBos(true)
808 .copyTtlOut()
809 .group(new DefaultGroupId(
Charles Chan5b9df8d2016-03-28 22:21:40 -0700810 onelabelGroupInfo.nextGroupDesc.givenGroupId()));
Charles Chan188ebf52015-12-23 00:15:11 -0800811 GroupBucket l3vpnGrpBkt =
812 DefaultGroupBucket.createIndirectGroupBucket(l3vpnTtb.build());
Saurav Das8be4e3a2016-03-11 17:19:07 -0800813 int l3vpnIndex = getNextAvailableIndex();
814 int l3vpngroupId = MPLS_L3VPN_SUBTYPE | (SUBTYPE_MASK & l3vpnIndex);
815 GroupKey l3vpngroupkey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700816 Ofdpa2Pipeline.appKryo.serialize(l3vpnIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800817 GroupDescription l3vpnGroupDesc =
818 new DefaultGroupDescription(
819 deviceId,
820 GroupDescription.Type.INDIRECT,
821 new GroupBuckets(Collections.singletonList(
822 l3vpnGrpBkt)),
823 l3vpngroupkey,
824 l3vpngroupId,
825 nextObj.appId());
826 GroupChainElem l3vpnGce = new GroupChainElem(l3vpnGroupDesc, 1, false);
Charles Chan5b9df8d2016-03-28 22:21:40 -0700827 updatePendingGroups(onelabelGroupInfo.nextGroupDesc.appCookie(), l3vpnGce);
Charles Chan188ebf52015-12-23 00:15:11 -0800828
Charles Chan5b9df8d2016-03-28 22:21:40 -0700829 gkeyChain.addFirst(onelabelGroupInfo.innerMostGroupDesc.appCookie());
830 gkeyChain.addFirst(onelabelGroupInfo.nextGroupDesc.appCookie());
Charles Chan188ebf52015-12-23 00:15:11 -0800831 gkeyChain.addFirst(l3vpngroupkey);
832
833 //now we can replace the outerGrpDesc with the one we just created
Charles Chan5b9df8d2016-03-28 22:21:40 -0700834 onelabelGroupInfo.nextGroupDesc = l3vpnGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -0800835
836 // we can't send the innermost group yet, as we have to create
837 // the dependent ECMP group first. So we store ...
838 unsentGroups.add(onelabelGroupInfo);
839
840 log.debug("Trying L3VPN: device:{} gid:{} gkey:{} nextId:{}",
841 deviceId, Integer.toHexString(l3vpngroupId),
842 l3vpngroupkey, nextObj.id());
843
844 } else {
845 log.warn("Driver currently does not handle more than 1 MPLS "
846 + "labels. Not processing nextObjective {}", nextObj.id());
847 return;
848 }
849
850 // all groups in this chain
851 allGroupKeys.add(gkeyChain);
852 }
853 }
854
Saurav Das8be4e3a2016-03-11 17:19:07 -0800855 //////////////////////////////////////
856 // Group Editing
857 //////////////////////////////////////
858
Charles Chan188ebf52015-12-23 00:15:11 -0800859 /**
860 * Adds a bucket to the top level group of a group-chain, and creates the chain.
861 *
862 * @param nextObjective the next group to add a bucket to
863 * @param next the representation of the existing group-chain for this next objective
864 */
865 protected void addBucketToGroup(NextObjective nextObjective, NextGroup next) {
866 if (nextObjective.type() != NextObjective.Type.HASHED) {
867 log.warn("AddBuckets not applied to nextType:{} in dev:{} for next:{}",
868 nextObjective.type(), deviceId, nextObjective.id());
869 return;
870 }
871 if (nextObjective.next().size() > 1) {
872 log.warn("Only one bucket can be added at a time");
873 return;
874 }
875 // storage for all group keys in the chain of groups created
876 List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
877 List<GroupInfo> unsentGroups = new ArrayList<>();
878 createHashBucketChains(nextObjective, allGroupKeys, unsentGroups);
879
880 // now we can create the outermost L3 ECMP group bucket to add
881 GroupInfo gi = unsentGroups.get(0); // only one bucket, so only one group-chain
882 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700883 ttb.group(new DefaultGroupId(gi.nextGroupDesc.givenGroupId()));
Charles Chan188ebf52015-12-23 00:15:11 -0800884 GroupBucket sbucket = DefaultGroupBucket.createSelectGroupBucket(ttb.build());
885
886 // recreate the original L3 ECMP group id and description
Charles Chane849c192016-01-11 18:28:54 -0800887 int l3ecmpGroupId = L3_ECMP_TYPE | nextObjective.id() << 12;
Charles Chan361154b2016-03-24 10:23:39 -0700888 GroupKey l3ecmpGroupKey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l3ecmpGroupId));
Charles Chan188ebf52015-12-23 00:15:11 -0800889
890 // Although GroupDescriptions are not necessary for adding buckets to
891 // existing groups, we use one in the GroupChainElem. When the latter is
892 // processed, the info will be extracted for the bucketAdd call to groupService
893 GroupDescription l3ecmpGroupDesc =
894 new DefaultGroupDescription(
895 deviceId,
896 GroupDescription.Type.SELECT,
897 new GroupBuckets(Collections.singletonList(sbucket)),
898 l3ecmpGroupKey,
899 l3ecmpGroupId,
900 nextObjective.appId());
901 GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc, 1, true);
902
903 // update original NextGroup with new bucket-chain
904 // don't need to update pendingNextObjectives -- group already exists
905 Deque<GroupKey> newBucketChain = allGroupKeys.get(0);
906 newBucketChain.addFirst(l3ecmpGroupKey);
Charles Chan361154b2016-03-24 10:23:39 -0700907 List<Deque<GroupKey>> allOriginalKeys = Ofdpa2Pipeline.appKryo.deserialize(next.data());
Charles Chan188ebf52015-12-23 00:15:11 -0800908 allOriginalKeys.add(newBucketChain);
909 flowObjectiveStore.putNextGroup(nextObjective.id(),
910 new OfdpaNextGroup(allOriginalKeys, nextObjective));
911
912 log.debug("Adding to L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
913 deviceId, Integer.toHexString(l3ecmpGroupId),
914 l3ecmpGroupKey, nextObjective.id());
915 // send the innermost group
916 log.debug("Sending innermost group {} in group chain on device {} ",
Charles Chan5b9df8d2016-03-28 22:21:40 -0700917 Integer.toHexString(gi.innerMostGroupDesc.givenGroupId()), deviceId);
918 updatePendingGroups(gi.nextGroupDesc.appCookie(), l3ecmpGce);
919 groupService.addGroup(gi.innerMostGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800920
921 }
922
923 /**
924 * Removes the bucket in the top level group of a possible group-chain. Does
925 * not remove the groups in a group-chain pointed to by this bucket, as they
926 * may be in use (referenced by other groups) elsewhere.
927 *
928 * @param nextObjective the next group to remove a bucket from
929 * @param next the representation of the existing group-chain for this next objective
930 */
931 protected void removeBucketFromGroup(NextObjective nextObjective, NextGroup next) {
932 if (nextObjective.type() != NextObjective.Type.HASHED) {
933 log.warn("RemoveBuckets not applied to nextType:{} in dev:{} for next:{}",
934 nextObjective.type(), deviceId, nextObjective.id());
935 return;
936 }
937 Collection<TrafficTreatment> treatments = nextObjective.next();
938 TrafficTreatment treatment = treatments.iterator().next();
939 // find the bucket to remove by noting the outport, and figuring out the
940 // top-level group in the group-chain that indirectly references the port
941 PortNumber outport = null;
942 for (Instruction ins : treatment.allInstructions()) {
943 if (ins instanceof Instructions.OutputInstruction) {
944 outport = ((Instructions.OutputInstruction) ins).port();
945 break;
946 }
947 }
948 if (outport == null) {
949 log.error("next objective {} has no outport", nextObjective.id());
950 return;
951 }
952
Charles Chan361154b2016-03-24 10:23:39 -0700953 List<Deque<GroupKey>> allgkeys = Ofdpa2Pipeline.appKryo.deserialize(next.data());
Charles Chan188ebf52015-12-23 00:15:11 -0800954 Deque<GroupKey> foundChain = null;
955 int index = 0;
956 for (Deque<GroupKey> gkeys : allgkeys) {
957 GroupKey groupWithPort = gkeys.peekLast();
958 Group group = groupService.getGroup(deviceId, groupWithPort);
959 if (group == null) {
960 log.warn("Inconsistent group chain");
961 continue;
962 }
963 // last group in group chain should have a single bucket pointing to port
964 List<Instruction> lastIns = group.buckets().buckets().iterator()
965 .next().treatment().allInstructions();
966 for (Instruction i : lastIns) {
967 if (i instanceof Instructions.OutputInstruction) {
968 PortNumber lastport = ((Instructions.OutputInstruction) i).port();
969 if (lastport.equals(outport)) {
970 foundChain = gkeys;
971 break;
972 }
973 }
974 }
975 if (foundChain != null) {
976 break;
977 }
978 index++;
979 }
980 if (foundChain != null) {
981 //first groupkey is the one we want to modify
982 GroupKey modGroupKey = foundChain.peekFirst();
983 Group modGroup = groupService.getGroup(deviceId, modGroupKey);
984 //second groupkey is the one we wish to remove the reference to
985 GroupKey pointedGroupKey = null;
986 int i = 0;
987 for (GroupKey gk : foundChain) {
988 if (i++ == 1) {
989 pointedGroupKey = gk;
990 break;
991 }
992 }
993 Group pointedGroup = groupService.getGroup(deviceId, pointedGroupKey);
994 GroupBucket bucket = DefaultGroupBucket.createSelectGroupBucket(
995 DefaultTrafficTreatment.builder()
996 .group(pointedGroup.id())
997 .build());
998 GroupBuckets removeBuckets = new GroupBuckets(Collections
999 .singletonList(bucket));
1000 log.debug("Removing buckets from group id {} for next id {} in device {}",
1001 modGroup.id(), nextObjective.id(), deviceId);
1002 groupService.removeBucketsFromGroup(deviceId, modGroupKey,
1003 removeBuckets, modGroupKey,
1004 nextObjective.appId());
1005 //update store
1006 allgkeys.remove(index);
1007 flowObjectiveStore.putNextGroup(nextObjective.id(),
1008 new OfdpaNextGroup(allgkeys, nextObjective));
1009 } else {
1010 log.warn("Could not find appropriate group-chain for removing bucket"
1011 + " for next id {} in dev:{}", nextObjective.id(), deviceId);
1012 }
1013 }
1014
1015 /**
1016 * Removes all groups in multiple possible group-chains that represent the next
1017 * objective.
1018 *
1019 * @param nextObjective the next objective to remove
1020 * @param next the NextGroup that represents the existing group-chain for
1021 * this next objective
1022 */
1023 protected void removeGroup(NextObjective nextObjective, NextGroup next) {
Charles Chan361154b2016-03-24 10:23:39 -07001024 List<Deque<GroupKey>> allgkeys = Ofdpa2Pipeline.appKryo.deserialize(next.data());
Charles Chanfc5c7802016-05-17 13:13:55 -07001025
1026 List<GroupKey> groupKeys = allgkeys.stream()
1027 .map(Deque::getFirst).collect(Collectors.toList());
1028 pendingRemoveNextObjectives.put(nextObjective, groupKeys);
1029
Charles Chan188ebf52015-12-23 00:15:11 -08001030 allgkeys.forEach(groupChain -> groupChain.forEach(groupKey ->
1031 groupService.removeGroup(deviceId, groupKey, nextObjective.appId())));
1032 flowObjectiveStore.removeNextGroup(nextObjective.id());
1033 }
1034
Saurav Das8be4e3a2016-03-11 17:19:07 -08001035 //////////////////////////////////////
1036 // Helper Methods and Classes
1037 //////////////////////////////////////
1038
1039 private void updatePendingNextObjective(GroupKey key, OfdpaNextGroup value) {
1040 List<OfdpaNextGroup> nextList = new CopyOnWriteArrayList<OfdpaNextGroup>();
1041 nextList.add(value);
Charles Chanfc5c7802016-05-17 13:13:55 -07001042 List<OfdpaNextGroup> ret = pendingAddNextObjectives.asMap()
Saurav Das8be4e3a2016-03-11 17:19:07 -08001043 .putIfAbsent(key, nextList);
1044 if (ret != null) {
1045 ret.add(value);
1046 }
1047 }
1048
Charles Chan425854b2016-04-11 15:32:12 -07001049 protected void updatePendingGroups(GroupKey gkey, GroupChainElem gce) {
Saurav Das8be4e3a2016-03-11 17:19:07 -08001050 Set<GroupChainElem> gceSet = Collections.newSetFromMap(
1051 new ConcurrentHashMap<GroupChainElem, Boolean>());
1052 gceSet.add(gce);
1053 Set<GroupChainElem> retval = pendingGroups.putIfAbsent(gkey, gceSet);
1054 if (retval != null) {
1055 retval.add(gce);
1056 }
1057 }
1058
Charles Chan188ebf52015-12-23 00:15:11 -08001059 /**
1060 * Processes next element of a group chain. Assumption is that if this
1061 * group points to another group, the latter has already been created
1062 * and this driver has received notification for it. A second assumption is
1063 * that if there is another group waiting for this group then the appropriate
1064 * stores already have the information to act upon the notification for the
1065 * creation of this group.
1066 * <p>
1067 * The processing of the GroupChainElement depends on the number of groups
1068 * this element is waiting on. For all group types other than SIMPLE, a
1069 * GroupChainElement could be waiting on multiple groups.
1070 *
1071 * @param gce the group chain element to be processed next
1072 */
1073 private void processGroupChain(GroupChainElem gce) {
1074 int waitOnGroups = gce.decrementAndGetGroupsWaitedOn();
1075 if (waitOnGroups != 0) {
1076 log.debug("GCE: {} not ready to be processed", gce);
1077 return;
1078 }
1079 log.debug("GCE: {} ready to be processed", gce);
1080 if (gce.addBucketToGroup) {
1081 groupService.addBucketsToGroup(gce.groupDescription.deviceId(),
1082 gce.groupDescription.appCookie(),
1083 gce.groupDescription.buckets(),
1084 gce.groupDescription.appCookie(),
1085 gce.groupDescription.appId());
1086 } else {
1087 groupService.addGroup(gce.groupDescription);
1088 }
1089 }
1090
1091 private class GroupChecker implements Runnable {
1092 @Override
1093 public void run() {
1094 Set<GroupKey> keys = pendingGroups.keySet().stream()
1095 .filter(key -> groupService.getGroup(deviceId, key) != null)
1096 .collect(Collectors.toSet());
Charles Chanfc5c7802016-05-17 13:13:55 -07001097 Set<GroupKey> otherkeys = pendingAddNextObjectives.asMap().keySet().stream()
Charles Chan188ebf52015-12-23 00:15:11 -08001098 .filter(otherkey -> groupService.getGroup(deviceId, otherkey) != null)
1099 .collect(Collectors.toSet());
1100 keys.addAll(otherkeys);
1101
1102 keys.stream().forEach(key ->
Charles Chanfc5c7802016-05-17 13:13:55 -07001103 processPendingAddGroupsOrNextObjs(key, false));
Charles Chan188ebf52015-12-23 00:15:11 -08001104 }
1105 }
1106
Saurav Das8be4e3a2016-03-11 17:19:07 -08001107 private class InnerGroupListener implements GroupListener {
1108 @Override
1109 public void event(GroupEvent event) {
1110 log.trace("received group event of type {}", event.type());
Charles Chanfc5c7802016-05-17 13:13:55 -07001111 switch (event.type()) {
1112 case GROUP_ADDED:
1113 processPendingAddGroupsOrNextObjs(event.subject().appCookie(), true);
1114 break;
1115 case GROUP_REMOVED:
1116 processPendingRemoveNextObjs(event.subject().appCookie());
1117 break;
1118 default:
1119 break;
Saurav Das8be4e3a2016-03-11 17:19:07 -08001120 }
1121 }
1122 }
1123
Charles Chanfc5c7802016-05-17 13:13:55 -07001124 private void processPendingAddGroupsOrNextObjs(GroupKey key, boolean added) {
Charles Chan188ebf52015-12-23 00:15:11 -08001125 //first check for group chain
1126 Set<GroupChainElem> gceSet = pendingGroups.remove(key);
1127 if (gceSet != null) {
1128 for (GroupChainElem gce : gceSet) {
Charles Chan216e3c82016-04-23 14:48:16 -07001129 log.debug("Group service {} group key {} in device {}. "
Saurav Das0fd79d92016-03-07 10:58:36 -08001130 + "Processing next group in group chain with group id 0x{}",
Charles Chan188ebf52015-12-23 00:15:11 -08001131 (added) ? "ADDED" : "processed",
1132 key, deviceId,
1133 Integer.toHexString(gce.groupDescription.givenGroupId()));
1134 processGroupChain(gce);
1135 }
1136 } else {
1137 // otherwise chain complete - check for waiting nextObjectives
Charles Chanfc5c7802016-05-17 13:13:55 -07001138 List<OfdpaNextGroup> nextGrpList = pendingAddNextObjectives.getIfPresent(key);
Charles Chan188ebf52015-12-23 00:15:11 -08001139 if (nextGrpList != null) {
Charles Chanfc5c7802016-05-17 13:13:55 -07001140 pendingAddNextObjectives.invalidate(key);
Charles Chan188ebf52015-12-23 00:15:11 -08001141 nextGrpList.forEach(nextGrp -> {
Charles Chan216e3c82016-04-23 14:48:16 -07001142 log.debug("Group service {} group key {} in device:{}. "
Saurav Das0fd79d92016-03-07 10:58:36 -08001143 + "Done implementing next objective: {} <<-->> gid:0x{}",
Charles Chan188ebf52015-12-23 00:15:11 -08001144 (added) ? "ADDED" : "processed",
1145 key, deviceId, nextGrp.nextObjective().id(),
1146 Integer.toHexString(groupService.getGroup(deviceId, key)
1147 .givenGroupId()));
Charles Chan361154b2016-03-24 10:23:39 -07001148 Ofdpa2Pipeline.pass(nextGrp.nextObjective());
Charles Chan188ebf52015-12-23 00:15:11 -08001149 flowObjectiveStore.putNextGroup(nextGrp.nextObjective().id(), nextGrp);
1150 // check if addBuckets waiting for this completion
1151 NextObjective pendBkt = pendingBuckets
1152 .remove(nextGrp.nextObjective().id());
1153 if (pendBkt != null) {
1154 addBucketToGroup(pendBkt, nextGrp);
1155 }
1156 });
1157 }
1158 }
1159 }
1160
Charles Chanfc5c7802016-05-17 13:13:55 -07001161 private void processPendingRemoveNextObjs(GroupKey key) {
1162 pendingRemoveNextObjectives.asMap().forEach((nextObjective, groupKeys) -> {
1163 if (groupKeys.isEmpty()) {
1164 pendingRemoveNextObjectives.invalidate(nextObjective);
1165 Ofdpa2Pipeline.pass(nextObjective);
1166 } else {
1167 groupKeys.remove(key);
1168 }
1169 });
1170 }
1171
Charles Chan425854b2016-04-11 15:32:12 -07001172 protected int getNextAvailableIndex() {
Saurav Das8be4e3a2016-03-11 17:19:07 -08001173 return (int) nextIndex.incrementAndGet();
1174 }
1175
Charles Chane849c192016-01-11 18:28:54 -08001176 /**
1177 * Returns a hash as the L2 Interface Group Key.
1178 *
1179 * Keep the lower 6-bit for port since port number usually smaller than 64.
1180 * Hash other information into remaining 28 bits.
1181 *
1182 * @param deviceId Device ID
1183 * @param vlanId VLAN ID
1184 * @param portNumber Port number
1185 * @return L2 interface group key
1186 */
Charles Chan425854b2016-04-11 15:32:12 -07001187 protected int l2InterfaceGroupKey(
Charles Chane849c192016-01-11 18:28:54 -08001188 DeviceId deviceId, VlanId vlanId, long portNumber) {
1189 int portLowerBits = (int) portNumber & PORT_LOWER_BITS_MASK;
1190 long portHigherBits = portNumber & PORT_HIGHER_BITS_MASK;
Charles Chand0fd5dc2016-02-16 23:14:49 -08001191 int hash = Objects.hash(deviceId, vlanId, portHigherBits);
Charles Chane849c192016-01-11 18:28:54 -08001192 return L2_INTERFACE_TYPE | (TYPE_MASK & hash << 6) | portLowerBits;
1193 }
1194
Charles Chan188ebf52015-12-23 00:15:11 -08001195 /**
1196 * Utility class for moving group information around.
1197 */
Charles Chan425854b2016-04-11 15:32:12 -07001198 protected class GroupInfo {
Charles Chan5b9df8d2016-03-28 22:21:40 -07001199 /**
1200 * Description of the inner-most group of the group chain.
1201 * It is always an L2 interface group.
1202 */
1203 private GroupDescription innerMostGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -08001204
Charles Chan5b9df8d2016-03-28 22:21:40 -07001205 /**
1206 * Description of the next group in the group chain.
1207 * It can be L2 interface, L3 interface, L3 unicast, L3 VPN group.
1208 * It is possible that nextGroup is the same as the innerMostGroup.
1209 */
1210 private GroupDescription nextGroupDesc;
1211
1212 GroupInfo(GroupDescription innerMostGroupDesc, GroupDescription nextGroupDesc) {
1213 this.innerMostGroupDesc = innerMostGroupDesc;
1214 this.nextGroupDesc = nextGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -08001215 }
1216 }
1217
1218 /**
1219 * Represents an entire group-chain that implements a Next-Objective from
1220 * the application. The objective is represented as a list of deques, where
1221 * each deque is a separate chain of groups.
1222 * <p>
1223 * For example, an ECMP group with 3 buckets, where each bucket points to
1224 * a group chain of L3 Unicast and L2 interface groups will look like this:
1225 * <ul>
1226 * <li>List[0] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1227 * <li>List[1] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1228 * <li>List[2] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1229 * </ul>
1230 * where the first element of each deque is the same, representing the
1231 * top level ECMP group, while every other element represents a unique groupKey.
1232 * <p>
1233 * Also includes information about the next objective that
1234 * resulted in this group-chain.
1235 *
1236 */
1237 protected class OfdpaNextGroup implements NextGroup {
1238 private final NextObjective nextObj;
1239 private final List<Deque<GroupKey>> gkeys;
1240
1241 public OfdpaNextGroup(List<Deque<GroupKey>> gkeys, NextObjective nextObj) {
1242 this.gkeys = gkeys;
1243 this.nextObj = nextObj;
1244 }
1245
Charles Chan188ebf52015-12-23 00:15:11 -08001246 public List<Deque<GroupKey>> groupKey() {
1247 return gkeys;
1248 }
1249
1250 public NextObjective nextObjective() {
1251 return nextObj;
1252 }
1253
1254 @Override
1255 public byte[] data() {
Charles Chan361154b2016-03-24 10:23:39 -07001256 return Ofdpa2Pipeline.appKryo.serialize(gkeys);
Charles Chan188ebf52015-12-23 00:15:11 -08001257 }
1258 }
1259
1260 /**
1261 * Represents a group element that is part of a chain of groups.
1262 * Stores enough information to create a Group Description to add the group
1263 * to the switch by requesting the Group Service. Objects instantiating this
1264 * class are meant to be temporary and live as long as it is needed to wait for
1265 * preceding groups in the group chain to be created.
1266 */
Charles Chan425854b2016-04-11 15:32:12 -07001267 protected class GroupChainElem {
Charles Chan188ebf52015-12-23 00:15:11 -08001268 private GroupDescription groupDescription;
1269 private AtomicInteger waitOnGroups;
1270 private boolean addBucketToGroup;
1271
1272 GroupChainElem(GroupDescription groupDescription, int waitOnGroups,
1273 boolean addBucketToGroup) {
1274 this.groupDescription = groupDescription;
1275 this.waitOnGroups = new AtomicInteger(waitOnGroups);
1276 this.addBucketToGroup = addBucketToGroup;
1277 }
1278
1279 /**
1280 * This methods atomically decrements the counter for the number of
1281 * groups this GroupChainElement is waiting on, for notifications from
1282 * the Group Service. When this method returns a value of 0, this
1283 * GroupChainElement is ready to be processed.
1284 *
1285 * @return integer indication of the number of notifications being waited on
1286 */
1287 int decrementAndGetGroupsWaitedOn() {
1288 return waitOnGroups.decrementAndGet();
1289 }
1290
1291 @Override
1292 public String toString() {
1293 return (Integer.toHexString(groupDescription.givenGroupId()) +
1294 " groupKey: " + groupDescription.appCookie() +
1295 " waiting-on-groups: " + waitOnGroups.get() +
1296 " addBucketToGroup: " + addBucketToGroup +
1297 " device: " + deviceId);
1298 }
1299 }
1300}