blob: df03656a6124715171c8ebfcd9bc0ae2236c886b [file] [log] [blame]
Yi Tsengef19de12017-04-24 11:33:05 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
Yi Tsengef19de12017-04-24 11:33:05 -07003 *
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 */
16
17package org.onosproject.driver.pipeline.ofdpa;
18
19import com.google.common.collect.ImmutableList;
20import com.google.common.collect.Lists;
21import com.google.common.collect.Sets;
22import org.onlab.packet.VlanId;
23import org.onosproject.core.GroupId;
24import org.onosproject.net.DeviceId;
25import org.onosproject.net.PortNumber;
26import org.onosproject.net.behaviour.NextGroup;
27import org.onosproject.net.flow.DefaultTrafficTreatment;
28import org.onosproject.net.flow.TrafficSelector;
29import org.onosproject.net.flow.TrafficTreatment;
30import org.onosproject.net.flow.instructions.Instruction;
31import org.onosproject.net.flow.instructions.Instructions;
Saurav Das7bcbe702017-06-13 15:35:54 -070032import org.onosproject.net.flow.instructions.L2ModificationInstruction;
33import org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType;
Yi Tsengef19de12017-04-24 11:33:05 -070034import org.onosproject.net.flowobjective.NextObjective;
35import org.onosproject.net.group.DefaultGroupBucket;
36import org.onosproject.net.group.DefaultGroupKey;
37import org.onosproject.net.group.Group;
38import org.onosproject.net.group.GroupBucket;
39import org.onosproject.net.group.GroupDescription;
40import org.onosproject.net.group.GroupKey;
41import org.onosproject.net.group.GroupService;
42import org.slf4j.Logger;
43
Saurav Dasceccf242017-08-03 18:30:35 -070044import java.util.ArrayList;
Yi Tsengef19de12017-04-24 11:33:05 -070045import java.util.Deque;
46import java.util.List;
47import java.util.Objects;
48import java.util.Set;
49import java.util.concurrent.atomic.AtomicInteger;
50import java.util.stream.Collectors;
51
52import static org.onosproject.driver.pipeline.ofdpa.Ofdpa2Pipeline.isNotMplsBos;
53import static org.onosproject.driver.pipeline.ofdpa.OfdpaGroupHandlerUtility.OfdpaMplsGroupSubType.OFDPA_GROUP_TYPE_SHIFT;
54import static org.onosproject.driver.pipeline.ofdpa.OfdpaGroupHandlerUtility.OfdpaMplsGroupSubType.OFDPA_MPLS_SUBTYPE_SHIFT;
55import static org.onosproject.net.flowobjective.NextObjective.Type.HASHED;
56import static org.slf4j.LoggerFactory.getLogger;
57
58public final class OfdpaGroupHandlerUtility {
59 /*
60 * OFDPA requires group-id's to have a certain form.
61 * L2 Interface Groups have <4bits-0><12bits-vlanId><16bits-portId>
62 * L3 Unicast Groups have <4bits-2><28bits-index>
63 * MPLS Interface Groups have <4bits-9><4bits:0><24bits-index>
64 * L3 ECMP Groups have <4bits-7><28bits-index>
65 * L2 Flood Groups have <4bits-4><12bits-vlanId><16bits-index>
66 * L3 VPN Groups have <4bits-9><4bits-2><24bits-index>
67 */
Ray Milkey9c9cde42018-01-12 14:22:06 -080068 static final int L2_INTERFACE_TYPE = 0x00000000;
69 static final int L3_INTERFACE_TYPE = 0x50000000;
70 static final int L3_UNICAST_TYPE = 0x20000000;
71 static final int L3_MULTICAST_TYPE = 0x60000000;
72 static final int MPLS_INTERFACE_TYPE = 0x90000000;
73 static final int MPLS_L3VPN_SUBTYPE = 0x92000000;
74 static final int L3_ECMP_TYPE = 0x70000000;
75 static final int L2_FLOOD_TYPE = 0x40000000;
Yi Tsengef19de12017-04-24 11:33:05 -070076
Ray Milkey9c9cde42018-01-12 14:22:06 -080077 static final int TYPE_MASK = 0x0fffffff;
78 static final int SUBTYPE_MASK = 0x00ffffff;
79 static final int TYPE_VLAN_MASK = 0x0000ffff;
Yi Tsengef19de12017-04-24 11:33:05 -070080
Ray Milkey9c9cde42018-01-12 14:22:06 -080081 static final int THREE_BIT_MASK = 0x0fff;
82 static final int FOUR_BIT_MASK = 0xffff;
83 static final int PORT_LEN = 16;
Yi Tsengef19de12017-04-24 11:33:05 -070084
Ray Milkey9c9cde42018-01-12 14:22:06 -080085 static final int PORT_LOWER_BITS_MASK = 0x3f;
86 static final long PORT_HIGHER_BITS_MASK = ~PORT_LOWER_BITS_MASK;
Yi Tsengef19de12017-04-24 11:33:05 -070087
Ray Milkey9c9cde42018-01-12 14:22:06 -080088 static final String HEX_PREFIX = "0x";
89 private static final Logger log = getLogger(OfdpaGroupHandlerUtility.class);
Yi Tsengef19de12017-04-24 11:33:05 -070090
91 private OfdpaGroupHandlerUtility() {
92 // Utility classes should not have a public or default constructor.
93 }
94
95 /**
96 * Returns the outport in a traffic treatment.
97 *
98 * @param tt the treatment
99 * @return the PortNumber for the outport or null
100 */
Ray Milkey9c9cde42018-01-12 14:22:06 -0800101 static PortNumber readOutPortFromTreatment(TrafficTreatment tt) {
Yi Tsengef19de12017-04-24 11:33:05 -0700102 for (Instruction ins : tt.allInstructions()) {
103 if (ins.type() == Instruction.Type.OUTPUT) {
104 return ((Instructions.OutputInstruction) ins).port();
105 }
106 }
107 return null;
108 }
109
110 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700111 * Returns the MPLS label-id in a traffic treatment.
112 *
113 * @param tt the traffic treatment
114 * @return an integer representing the MPLS label-id, or -1 if not found
115 */
Ray Milkey9c9cde42018-01-12 14:22:06 -0800116 static int readLabelFromTreatment(TrafficTreatment tt) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700117 for (Instruction ins : tt.allInstructions()) {
118 if (ins.type() == Instruction.Type.L2MODIFICATION) {
119 L2ModificationInstruction insl2 = (L2ModificationInstruction) ins;
120 if (insl2.subtype() == L2SubType.MPLS_LABEL) {
121 return ((L2ModificationInstruction.ModMplsLabelInstruction) insl2)
122 .label().id();
123 }
124 }
125 }
126 return -1;
127 }
128
129 /**
Yi Tsengef19de12017-04-24 11:33:05 -0700130 * Helper enum to handle the different MPLS group
131 * types.
132 */
133 public enum OfdpaMplsGroupSubType {
134 MPLS_INTF((short) 0),
135 L2_VPN((short) 1),
136 L3_VPN((short) 2),
137 MPLS_TUNNEL_LABEL_1((short) 3),
138 MPLS_TUNNEL_LABEL_2((short) 4),
139 MPLS_SWAP_LABEL((short) 5),
140 MPLS_ECMP((short) 8);
141
142 private short value;
143 public static final int OFDPA_GROUP_TYPE_SHIFT = 28;
144 public static final int OFDPA_MPLS_SUBTYPE_SHIFT = 24;
145
146 OfdpaMplsGroupSubType(short value) {
147 this.value = value;
148 }
149
150 /**
151 * Gets the value as an short.
152 *
153 * @return the value as an short
154 */
155 public short getValue() {
156 return this.value;
157 }
158
159 }
160
161 /**
162 * Creates MPLS Label group id given a sub type and
163 * the index.
164 *
165 * @param subType the MPLS Label group sub type
166 * @param index the index of the group
167 * @return the OFDPA group id
168 */
169 public static Integer makeMplsLabelGroupId(OfdpaMplsGroupSubType subType, int index) {
170 index = index & 0x00FFFFFF;
171 return index | (9 << OFDPA_GROUP_TYPE_SHIFT) | (subType.value << OFDPA_MPLS_SUBTYPE_SHIFT);
172 }
173
174 /**
175 * Creates MPLS Forwarding group id given a sub type and
176 * the index.
177 *
178 * @param subType the MPLS forwarding group sub type
179 * @param index the index of the group
180 * @return the OFDPA group id
181 */
182 public static Integer makeMplsForwardingGroupId(OfdpaMplsGroupSubType subType, int index) {
183 index = index & 0x00FFFFFF;
184 return index | (10 << OFDPA_GROUP_TYPE_SHIFT) | (subType.value << OFDPA_MPLS_SUBTYPE_SHIFT);
185 }
186
187 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700188 * Returns the set of existing output ports in the group represented by
189 * allActiveKeys.
Yi Tsengef19de12017-04-24 11:33:05 -0700190 *
191 * @param allActiveKeys list of group key chain
192 * @param groupService the group service to get group information
193 * @param deviceId the device id to get group
194 * @return a set of output port from the list of group key chain
195 */
196 public static Set<PortNumber> getExistingOutputPorts(List<Deque<GroupKey>> allActiveKeys,
197 GroupService groupService,
198 DeviceId deviceId) {
199 Set<PortNumber> existingPorts = Sets.newHashSet();
200
201 allActiveKeys.forEach(keyChain -> {
202 GroupKey ifaceGroupKey = keyChain.peekLast();
203 Group ifaceGroup = groupService.getGroup(deviceId, ifaceGroupKey);
204 if (ifaceGroup != null && !ifaceGroup.buckets().buckets().isEmpty()) {
205 ifaceGroup.buckets().buckets().forEach(bucket -> {
206 PortNumber portNumber = readOutPortFromTreatment(bucket.treatment());
207 if (portNumber != null) {
208 existingPorts.add(portNumber);
209 }
210 });
211 }
212 });
213 return existingPorts;
214 }
215
216 /**
Saurav Dasceccf242017-08-03 18:30:35 -0700217 * Returns a list of all indices in the allActiveKeys list (that represents
218 * a group) if the list element (a bucket or group-chain) has treatments
219 * that match the given outport and label.
Saurav Das7bcbe702017-06-13 15:35:54 -0700220 *
221 * @param allActiveKeys the representation of the group
222 * @param groupService groups service for querying group information
223 * @param deviceId the device id for the device that contains the group
224 * @param portToMatch the port to match in the group buckets
225 * @param labelToMatch the MPLS label-id to match in the group buckets
Saurav Dasceccf242017-08-03 18:30:35 -0700226 * @return a list of indexes in the allActiveKeys list where the list element
227 * has treatments that match the given portToMatch and labelToMatch.
228 * Could be empty if no list elements were found to match the given
229 * port and label.
Saurav Das7bcbe702017-06-13 15:35:54 -0700230 */
Saurav Dasceccf242017-08-03 18:30:35 -0700231 public static List<Integer> existingPortAndLabel(
232 List<Deque<GroupKey>> allActiveKeys,
Saurav Das7bcbe702017-06-13 15:35:54 -0700233 GroupService groupService,
234 DeviceId deviceId,
235 PortNumber portToMatch,
236 int labelToMatch) {
Saurav Dasceccf242017-08-03 18:30:35 -0700237 List<Integer> indices = new ArrayList<>();
238 int index = 0;
Saurav Das7bcbe702017-06-13 15:35:54 -0700239 for (Deque<GroupKey> keyChain : allActiveKeys) {
240 GroupKey ifaceGroupKey = keyChain.peekLast();
241 Group ifaceGroup = groupService.getGroup(deviceId, ifaceGroupKey);
242 if (ifaceGroup != null && !ifaceGroup.buckets().buckets().isEmpty()) {
243 PortNumber portNumber = readOutPortFromTreatment(
244 ifaceGroup.buckets().buckets().iterator().next().treatment());
245 if (portNumber != null && portNumber.equals(portToMatch)) {
246 // check for label in the 2nd group of this chain
247 GroupKey secondKey = (GroupKey) keyChain.toArray()[1];
248 Group secondGroup = groupService.getGroup(deviceId, secondKey);
249 if (secondGroup != null &&
250 !secondGroup.buckets().buckets().isEmpty()) {
251 int label = readLabelFromTreatment(
252 secondGroup.buckets().buckets()
253 .iterator().next().treatment());
254 if (label == labelToMatch) {
Saurav Dasceccf242017-08-03 18:30:35 -0700255 indices.add(index);
Saurav Das7bcbe702017-06-13 15:35:54 -0700256 }
257 }
258 }
259 }
Saurav Dasceccf242017-08-03 18:30:35 -0700260 index++;
Saurav Das7bcbe702017-06-13 15:35:54 -0700261 }
262
Saurav Dasceccf242017-08-03 18:30:35 -0700263 return indices;
Saurav Das7bcbe702017-06-13 15:35:54 -0700264 }
265
266 /**
Yi Tsengef19de12017-04-24 11:33:05 -0700267 * The purpose of this function is to verify if the hashed next
268 * objective is supported by the current pipeline.
269 *
270 * @param nextObjective the hashed objective to verify
271 * @return true if the hashed objective is supported. Otherwise false.
272 */
273 public static boolean verifyHashedNextObjective(NextObjective nextObjective) {
274 // if it is not hashed, there is something wrong;
275 if (nextObjective.type() != HASHED) {
276 return false;
277 }
278 // The case non supported is the MPLS-ECMP. For now, we try
279 // to create a MPLS-ECMP for the transport of a VPWS. The
280 // necessary info are contained in the meta selector. In particular
281 // we are looking for the case of BoS==False;
282 TrafficSelector metaSelector = nextObjective.meta();
283 if (metaSelector != null && isNotMplsBos(metaSelector)) {
284 return false;
285 }
286
287 return true;
288 }
289
290 /**
291 * Generates a list of group buckets from given list of group information
292 * and group bucket type.
293 *
294 * @param groupInfos a list of group information
295 * @param bucketType group bucket type
296 * @return list of group bucket generate from group information
297 */
Ray Milkey9c9cde42018-01-12 14:22:06 -0800298 static List<GroupBucket> generateNextGroupBuckets(List<GroupInfo> groupInfos,
Yi Tsengef19de12017-04-24 11:33:05 -0700299 GroupDescription.Type bucketType) {
300 List<GroupBucket> newBuckets = Lists.newArrayList();
301
302 groupInfos.forEach(groupInfo -> {
303 GroupDescription groupDesc = groupInfo.nextGroupDesc();
304 TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
305 treatmentBuilder.group(new GroupId(groupDesc.givenGroupId()));
306 GroupBucket newBucket = null;
307 switch (bucketType) {
308 case ALL:
309 newBucket =
310 DefaultGroupBucket.createAllGroupBucket(treatmentBuilder.build());
311 break;
312 case INDIRECT:
313 newBucket =
314 DefaultGroupBucket.createIndirectGroupBucket(treatmentBuilder.build());
315 break;
316 case SELECT:
317 newBucket =
318 DefaultGroupBucket.createSelectGroupBucket(treatmentBuilder.build());
319 break;
320 case FAILOVER:
321 // TODO: support failover bucket type
322 default:
323 log.warn("Unknown bucket type: {}", bucketType);
324 break;
325 }
326
327 if (newBucket != null) {
328 newBuckets.add(newBucket);
329 }
330
331 });
332
333 return ImmutableList.copyOf(newBuckets);
334 }
335
336 /**
337 * Extracts VlanId from given group ID.
338 *
339 * @param groupId the group ID
340 * @return vlan id of the group
341 */
342 public static VlanId extractVlanIdFromGroupId(int groupId) {
343 // Extract the 9th to 20th bit from group id as vlan id.
344 short vlanId = (short) ((groupId & 0x0fff0000) >> 16);
345 return VlanId.vlanId(vlanId);
346 }
347
348 public static GroupKey l2FloodGroupKey(VlanId vlanId, DeviceId deviceId) {
349 int hash = Objects.hash(deviceId, vlanId);
350 hash = L2_FLOOD_TYPE | TYPE_MASK & hash;
351 return new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(hash));
352 }
353
354 public static int l2GroupId(VlanId vlanId, long portNum) {
355 return L2_INTERFACE_TYPE | (vlanId.toShort() << 16) | (int) portNum;
356 }
357
358 /**
359 * Returns a hash as the L2 Interface Group Key.
360 *
361 * Keep the lower 6-bit for port since port number usually smaller than 64.
362 * Hash other information into remaining 28 bits.
363 *
364 * @param deviceId Device ID
365 * @param vlanId VLAN ID
366 * @param portNumber Port number
367 * @return L2 interface group key
368 */
369 public static int l2InterfaceGroupKey(DeviceId deviceId, VlanId vlanId, long portNumber) {
370 int portLowerBits = (int) portNumber & PORT_LOWER_BITS_MASK;
371 long portHigherBits = portNumber & PORT_HIGHER_BITS_MASK;
372 int hash = Objects.hash(deviceId, vlanId, portHigherBits);
373 return L2_INTERFACE_TYPE | (TYPE_MASK & hash << 6) | portLowerBits;
374 }
375
376 /**
377 * Utility class for moving group information around.
378 *
379 * Example: Suppose we are trying to create a group-chain A-B-C-D, where
380 * A is the top level group, and D is the inner-most group, typically L2 Interface.
381 * The innerMostGroupDesc is always D. At various stages of the creation
382 * process the nextGroupDesc may be C or B. The nextGroupDesc exists to
383 * inform the referencing group about which group it needs to point to,
384 * and wait for. In some cases the group chain may simply be A-B. In this case,
385 * both innerMostGroupDesc and nextGroupDesc will be B.
386 */
387 public static class GroupInfo {
388 /**
389 * Description of the inner-most group of the group chain.
390 * It is always an L2 interface group.
391 */
392 private GroupDescription innerMostGroupDesc;
393
394 /**
395 * Description of the next group in the group chain.
396 * It can be L2 interface, L3 interface, L3 unicast, L3 VPN group.
397 * It is possible that nextGroupDesc is the same as the innerMostGroup.
398 */
399 private GroupDescription nextGroupDesc;
400
401 GroupInfo(GroupDescription innerMostGroupDesc, GroupDescription nextGroupDesc) {
402 this.innerMostGroupDesc = innerMostGroupDesc;
403 this.nextGroupDesc = nextGroupDesc;
404 }
405
406 /**
407 * Getter for innerMostGroupDesc.
408 *
409 * @return the inner most group description
410 */
411 public GroupDescription innerMostGroupDesc() {
412 return innerMostGroupDesc;
413 }
414
415 /**
416 * Getter for the next group description.
417 *
418 * @return the next group description
419 */
420 public GroupDescription nextGroupDesc() {
421 return nextGroupDesc;
422 }
423
424 /**
425 * Setter of nextGroupDesc.
426 *
427 * @param nextGroupDesc the given value to set
428 */
429 public void nextGroupDesc(GroupDescription nextGroupDesc) {
430 this.nextGroupDesc = nextGroupDesc;
431 }
432 }
433
434 /**
435 * Represents an entire group-chain that implements a Next-Objective from
436 * the application. The objective is represented as a list of deques, where
437 * each deque is a separate chain of groups.
438 * <p>
439 * For example, an ECMP group with 3 buckets, where each bucket points to
440 * a group chain of L3 Unicast and L2 interface groups will look like this:
441 * <ul>
442 * <li>List[0] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
443 * <li>List[1] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
444 * <li>List[2] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
445 * </ul>
446 * where the first element of each deque is the same, representing the
447 * top level ECMP group, while every other element represents a unique groupKey.
448 * <p>
449 * Also includes information about the next objective that
450 * resulted in these group-chains.
451 *
452 */
453 public static class OfdpaNextGroup implements NextGroup {
454 private final NextObjective nextObj;
455 private final List<Deque<GroupKey>> gkeys;
456
457 public OfdpaNextGroup(List<Deque<GroupKey>> gkeys, NextObjective nextObj) {
458 this.nextObj = nextObj;
459 this.gkeys = gkeys;
460 }
461
462 public NextObjective nextObjective() {
463 return nextObj;
464 }
465
Saurav Dasc88d4662017-05-15 15:34:25 -0700466 public List<Deque<GroupKey>> allKeys() {
467 return gkeys;
468 }
469
Yi Tsengef19de12017-04-24 11:33:05 -0700470 @Override
471 public byte[] data() {
472 return Ofdpa2Pipeline.appKryo.serialize(gkeys);
473 }
474 }
475
476 /**
477 * Represents a group element that is part of a chain of groups.
478 * Stores enough information to create a Group Description to add the group
479 * to the switch by requesting the Group Service. Objects instantiating this
480 * class are meant to be temporary and live as long as it is needed to wait for
481 * referenced groups in the group chain to be created.
482 */
483 public static class GroupChainElem {
484 private GroupDescription groupDescription;
485 private AtomicInteger waitOnGroups;
486 private boolean addBucketToGroup;
487 private DeviceId deviceId;
488
489 public GroupChainElem(GroupDescription groupDescription, int waitOnGroups,
490 boolean addBucketToGroup, DeviceId deviceId) {
491 this.groupDescription = groupDescription;
492 this.waitOnGroups = new AtomicInteger(waitOnGroups);
493 this.addBucketToGroup = addBucketToGroup;
494 this.deviceId = deviceId;
495 }
496
497 /**
498 * This method atomically decrements the counter for the number of
499 * groups this GroupChainElement is waiting on, for notifications from
500 * the Group Service. When this method returns a value of 0, this
501 * GroupChainElement is ready to be processed.
502 *
503 * @return integer indication of the number of notifications being waited on
504 */
505 int decrementAndGetGroupsWaitedOn() {
506 return waitOnGroups.decrementAndGet();
507 }
508
509 public GroupDescription groupDescription() {
510 return groupDescription;
511 }
512
513 public boolean addBucketToGroup() {
514 return addBucketToGroup;
515 }
516
517 @Override
518 public String toString() {
519 return (Integer.toHexString(groupDescription.givenGroupId()) +
520 " groupKey: " + groupDescription.appCookie() +
521 " waiting-on-groups: " + waitOnGroups.get() +
522 " addBucketToGroup: " + addBucketToGroup +
523 " device: " + deviceId);
524 }
525 }
526
527 public static class GroupChecker implements Runnable {
Ray Milkey9c9cde42018-01-12 14:22:06 -0800528 final Logger log = getLogger(getClass());
Yi Tsengef19de12017-04-24 11:33:05 -0700529 private Ofdpa2GroupHandler groupHandler;
530
531 public GroupChecker(Ofdpa2GroupHandler groupHandler) {
532 this.groupHandler = groupHandler;
533 }
534
535 @Override
536 public void run() {
Pier Luigi07532ab2018-01-12 16:03:49 +0100537 // GroupChecker execution needs to be protected
538 // from unhandled exceptions
539 try {
540 if (groupHandler.pendingGroups().size() != 0) {
541 log.debug("pending groups being checked: {}", groupHandler.pendingGroups().asMap().keySet());
542 }
543 if (groupHandler.pendingAddNextObjectives().size() != 0) {
544 log.debug("pending add-next-obj being checked: {}",
545 groupHandler.pendingAddNextObjectives().asMap().keySet());
546 }
547 Set<GroupKey> keys = groupHandler.pendingGroups().asMap().keySet().stream()
548 .filter(key -> groupHandler.groupService.getGroup(groupHandler.deviceId, key) != null)
549 .collect(Collectors.toSet());
550 Set<GroupKey> otherkeys = groupHandler.pendingAddNextObjectives().asMap().keySet().stream()
551 .filter(otherkey -> groupHandler.groupService.getGroup(groupHandler.deviceId, otherkey) != null)
552 .collect(Collectors.toSet());
553 keys.addAll(otherkeys);
Yi Tsengef19de12017-04-24 11:33:05 -0700554
Pier Luigi07532ab2018-01-12 16:03:49 +0100555 keys.forEach(key -> groupHandler.processPendingAddGroupsOrNextObjs(key, false));
556 } catch (Exception exception) {
557 // Just log. It is safe for now.
558 log.warn("Uncaught exception is detected: {}", exception.getMessage());
559 log.debug("Uncaught exception is detected (full stack trace): ", exception);
560 }
Yi Tsengef19de12017-04-24 11:33:05 -0700561 }
562 }
563}