| /* |
| * Copyright 2017-present Open Networking Foundation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.onosproject.driver.pipeline.ofdpa; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Sets; |
| import org.onlab.packet.VlanId; |
| import org.onosproject.core.GroupId; |
| import org.onosproject.net.DeviceId; |
| import org.onosproject.net.PortNumber; |
| import org.onosproject.net.behaviour.NextGroup; |
| import org.onosproject.net.flow.DefaultTrafficTreatment; |
| import org.onosproject.net.flow.TrafficSelector; |
| import org.onosproject.net.flow.TrafficTreatment; |
| import org.onosproject.net.flow.criteria.Criterion; |
| import org.onosproject.net.flow.criteria.VlanIdCriterion; |
| import org.onosproject.net.flow.instructions.Instruction; |
| import org.onosproject.net.flow.instructions.Instructions; |
| import org.onosproject.net.flow.instructions.L2ModificationInstruction; |
| import org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType; |
| import org.onosproject.net.flowobjective.NextObjective; |
| import org.onosproject.net.group.DefaultGroupBucket; |
| import org.onosproject.net.group.DefaultGroupKey; |
| import org.onosproject.net.group.Group; |
| import org.onosproject.net.group.GroupBucket; |
| import org.onosproject.net.group.GroupDescription; |
| import org.onosproject.net.group.GroupKey; |
| import org.onosproject.net.group.GroupService; |
| import org.slf4j.Logger; |
| |
| import java.util.ArrayList; |
| import java.util.Deque; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.stream.Collectors; |
| |
| import static org.onosproject.driver.pipeline.ofdpa.OfdpaPipelineUtility.isNotMplsBos; |
| import static org.onosproject.driver.pipeline.ofdpa.OfdpaGroupHandlerUtility.OfdpaMplsGroupSubType.OFDPA_GROUP_TYPE_SHIFT; |
| import static org.onosproject.driver.pipeline.ofdpa.OfdpaGroupHandlerUtility.OfdpaMplsGroupSubType.OFDPA_MPLS_SUBTYPE_SHIFT; |
| import static org.onosproject.net.flowobjective.NextObjective.Type.HASHED; |
| import static org.slf4j.LoggerFactory.getLogger; |
| |
| public final class OfdpaGroupHandlerUtility { |
| /* |
| * OFDPA requires group-id's to have a certain form. |
| * L2 Interface Groups have <4bits-0><12bits-vlanId><16bits-portId> |
| * L3 Unicast Groups have <4bits-2><28bits-index> |
| * L3 Unicast Groups for double-vlan have <4bits-2><1bit-1><12bits-vlanId><15bits-portId> |
| * MPLS Interface Groups have <4bits-9><4bits:0><24bits-index> |
| * L3 ECMP Groups have <4bits-7><28bits-index> |
| * L2 Flood Groups have <4bits-4><12bits-vlanId><16bits-index> |
| * L3 VPN Groups have <4bits-9><4bits-2><24bits-index> |
| */ |
| static final int L2_INTERFACE_TYPE = 0x00000000; |
| static final int L2_UNFILTERED_TYPE = 0xb0000000; |
| static final int L3_INTERFACE_TYPE = 0x50000000; |
| static final int L3_UNICAST_TYPE = 0x20000000; |
| static final int L3_MULTICAST_TYPE = 0x60000000; |
| static final int MPLS_INTERFACE_TYPE = 0x90000000; |
| static final int MPLS_L3VPN_SUBTYPE = 0x92000000; |
| static final int L3_ECMP_TYPE = 0x70000000; |
| static final int L2_FLOOD_TYPE = 0x40000000; |
| static final int L2_MULTICAST_TYPE = 0x30000000; |
| static final int L2_LB_TYPE = 0xc0000000; |
| |
| static final int TYPE_MASK = 0x0fffffff; |
| static final int SUBTYPE_MASK = 0x00ffffff; |
| static final int TYPE_VLAN_MASK = 0x0000ffff; |
| static final int TYPE_L3UG_DOUBLE_VLAN_MASK = 0x08000000; |
| |
| static final int THREE_NIBBLE_MASK = 0x0fff; |
| static final int FOUR_NIBBLE_MASK = 0xffff; |
| static final int PORT_LEN = 16; |
| |
| static final int PORT_LOWER_BITS_MASK = 0x3f; |
| static final long PORT_HIGHER_BITS_MASK = ~PORT_LOWER_BITS_MASK; |
| |
| static final String HEX_PREFIX = "0x"; |
| private static final Logger log = getLogger(OfdpaGroupHandlerUtility.class); |
| |
| private OfdpaGroupHandlerUtility() { |
| // Utility classes should not have a public or default constructor. |
| } |
| |
| /** |
| * Returns the outport in a traffic treatment. |
| * |
| * @param tt the treatment |
| * @return the PortNumber for the outport or null |
| */ |
| static PortNumber readOutPortFromTreatment(TrafficTreatment tt) { |
| for (Instruction ins : tt.allInstructions()) { |
| if (ins.type() == Instruction.Type.OUTPUT) { |
| return ((Instructions.OutputInstruction) ins).port(); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the MPLS label-id in a traffic treatment. |
| * |
| * @param tt the traffic treatment |
| * @return an integer representing the MPLS label-id, or -1 if not found |
| */ |
| static int readLabelFromTreatment(TrafficTreatment tt) { |
| for (Instruction ins : tt.allInstructions()) { |
| if (ins.type() == Instruction.Type.L2MODIFICATION) { |
| L2ModificationInstruction insl2 = (L2ModificationInstruction) ins; |
| if (insl2.subtype() == L2SubType.MPLS_LABEL) { |
| return ((L2ModificationInstruction.ModMplsLabelInstruction) insl2) |
| .label().id(); |
| } |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * Helper enum to handle the different MPLS group |
| * types. |
| */ |
| public enum OfdpaMplsGroupSubType { |
| MPLS_INTF((short) 0), |
| L2_VPN((short) 1), |
| L3_VPN((short) 2), |
| MPLS_TUNNEL_LABEL_1((short) 3), |
| MPLS_TUNNEL_LABEL_2((short) 4), |
| MPLS_SWAP_LABEL((short) 5), |
| MPLS_ECMP((short) 8); |
| |
| private short value; |
| public static final int OFDPA_GROUP_TYPE_SHIFT = 28; |
| public static final int OFDPA_MPLS_SUBTYPE_SHIFT = 24; |
| |
| OfdpaMplsGroupSubType(short value) { |
| this.value = value; |
| } |
| |
| /** |
| * Gets the value as an short. |
| * |
| * @return the value as an short |
| */ |
| public short getValue() { |
| return this.value; |
| } |
| |
| } |
| |
| /** |
| * Creates MPLS Label group id given a sub type and |
| * the index. |
| * |
| * @param subType the MPLS Label group sub type |
| * @param index the index of the group |
| * @return the OFDPA group id |
| */ |
| public static Integer makeMplsLabelGroupId(OfdpaMplsGroupSubType subType, int index) { |
| index = index & 0x00FFFFFF; |
| return index | (9 << OFDPA_GROUP_TYPE_SHIFT) | (subType.value << OFDPA_MPLS_SUBTYPE_SHIFT); |
| } |
| |
| /** |
| * Creates MPLS Forwarding group id given a sub type and |
| * the index. |
| * |
| * @param subType the MPLS forwarding group sub type |
| * @param index the index of the group |
| * @return the OFDPA group id |
| */ |
| public static Integer makeMplsForwardingGroupId(OfdpaMplsGroupSubType subType, int index) { |
| index = index & 0x00FFFFFF; |
| return index | (10 << OFDPA_GROUP_TYPE_SHIFT) | (subType.value << OFDPA_MPLS_SUBTYPE_SHIFT); |
| } |
| |
| /** |
| * Returns the set of existing output ports in the group represented by |
| * allActiveKeys. |
| * |
| * @param allActiveKeys list of group key chain |
| * @param groupService the group service to get group information |
| * @param deviceId the device id to get group |
| * @return a set of output port from the list of group key chain |
| */ |
| public static Set<PortNumber> getExistingOutputPorts(List<Deque<GroupKey>> allActiveKeys, |
| GroupService groupService, |
| DeviceId deviceId) { |
| Set<PortNumber> existingPorts = Sets.newHashSet(); |
| |
| allActiveKeys.forEach(keyChain -> { |
| GroupKey ifaceGroupKey = keyChain.peekLast(); |
| Group ifaceGroup = groupService.getGroup(deviceId, ifaceGroupKey); |
| if (ifaceGroup != null && !ifaceGroup.buckets().buckets().isEmpty()) { |
| ifaceGroup.buckets().buckets().forEach(bucket -> { |
| PortNumber portNumber = readOutPortFromTreatment(bucket.treatment()); |
| if (portNumber != null) { |
| existingPorts.add(portNumber); |
| } |
| }); |
| } |
| }); |
| return existingPorts; |
| } |
| |
| /** |
| * Returns a list of all indices in the allActiveKeys list (that represents |
| * a group) if the list element (a bucket or group-chain) has treatments |
| * that match the given outport and label. |
| * |
| * @param allActiveKeys the representation of the group |
| * @param groupService groups service for querying group information |
| * @param deviceId the device id for the device that contains the group |
| * @param portToMatch the port to match in the group buckets |
| * @param labelToMatch the MPLS label-id to match in the group buckets |
| * @return a list of indexes in the allActiveKeys list where the list element |
| * has treatments that match the given portToMatch and labelToMatch. |
| * Could be empty if no list elements were found to match the given |
| * port and label. |
| */ |
| public static List<Integer> existingPortAndLabel( |
| List<Deque<GroupKey>> allActiveKeys, |
| GroupService groupService, |
| DeviceId deviceId, |
| PortNumber portToMatch, |
| int labelToMatch) { |
| List<Integer> indices = new ArrayList<>(); |
| int index = 0; |
| for (Deque<GroupKey> keyChain : allActiveKeys) { |
| GroupKey ifaceGroupKey = keyChain.peekLast(); |
| Group ifaceGroup = groupService.getGroup(deviceId, ifaceGroupKey); |
| if (ifaceGroup != null && !ifaceGroup.buckets().buckets().isEmpty()) { |
| PortNumber portNumber = readOutPortFromTreatment( |
| ifaceGroup.buckets().buckets().iterator().next().treatment()); |
| if (portNumber != null && portNumber.equals(portToMatch)) { |
| // check for label in the 2nd group of this chain |
| GroupKey secondKey = (GroupKey) keyChain.toArray()[1]; |
| Group secondGroup = groupService.getGroup(deviceId, secondKey); |
| if (secondGroup != null && |
| !secondGroup.buckets().buckets().isEmpty()) { |
| int label = readLabelFromTreatment( |
| secondGroup.buckets().buckets() |
| .iterator().next().treatment()); |
| if (label == labelToMatch) { |
| indices.add(index); |
| } |
| } |
| } |
| } |
| index++; |
| } |
| |
| return indices; |
| } |
| |
| /** |
| * Get indices to remove comparing next group with next objective. |
| * |
| * @param allActiveKeys the representation of the group |
| * @param nextObjective the next objective to verify |
| * @param groupService groups service for querying group information |
| * @param deviceId the device id for the device that contains the group |
| * @return a list of indexes in the allActiveKeys to remove. |
| */ |
| public static List<Integer> indicesToRemoveFromNextGroup(List<Deque<GroupKey>> allActiveKeys, |
| NextObjective nextObjective, |
| GroupService groupService, |
| DeviceId deviceId) { |
| List<Integer> indicesToRemove = Lists.newArrayList(); |
| int index = 0; |
| // Iterate over the chain in the next data |
| for (Deque<GroupKey> keyChain : allActiveKeys) { |
| // Valid chain should have at least two elements |
| if (keyChain.size() >= 2) { |
| // Get last group (l2if) and retrieve port number |
| GroupKey ifaceGroupKey = keyChain.peekLast(); |
| Group ifaceGroup = groupService.getGroup(deviceId, ifaceGroupKey); |
| if (ifaceGroup != null && !ifaceGroup.buckets().buckets().isEmpty()) { |
| PortNumber portNumber = readOutPortFromTreatment( |
| ifaceGroup.buckets().buckets().iterator().next().treatment()); |
| // If there is not a port number continue |
| if (portNumber != null) { |
| // check for label in the 2nd group of this chain |
| GroupKey secondKey = (GroupKey) keyChain.toArray()[1]; |
| Group secondGroup = groupService.getGroup(deviceId, secondKey); |
| // If there is not a second group or there are no buckets continue |
| if (secondGroup != null && !secondGroup.buckets().buckets().isEmpty()) { |
| // Get label or -1 |
| int label = readLabelFromTreatment( |
| secondGroup.buckets().buckets() |
| .iterator().next().treatment()); |
| // Iterate over the next treatments looking for the port and the label |
| boolean matches = false; |
| for (TrafficTreatment t : nextObjective.next()) { |
| PortNumber tPort = readOutPortFromTreatment(t); |
| int tLabel = readLabelFromTreatment(t); |
| if (tPort != null && tPort.equals(portNumber) && tLabel == label) { |
| // We found it, exit |
| matches = true; |
| break; |
| } |
| } |
| // Not found, we have to remove it |
| if (!matches) { |
| indicesToRemove.add(index); |
| } |
| } |
| } |
| } |
| } |
| index++; |
| } |
| return indicesToRemove; |
| } |
| |
| /** |
| * The purpose of this function is to verify if the hashed next |
| * objective is supported by the current pipeline. |
| * |
| * @param nextObjective the hashed objective to verify |
| * @return true if the hashed objective is supported. Otherwise false. |
| */ |
| public static boolean verifyHashedNextObjective(NextObjective nextObjective) { |
| // if it is not hashed, there is something wrong; |
| if (nextObjective.type() != HASHED) { |
| return false; |
| } |
| // The case non supported is the MPLS-ECMP. For now, we try |
| // to create a MPLS-ECMP for the transport of a VPWS. The |
| // necessary info are contained in the meta selector. In particular |
| // we are looking for the case of BoS==False; |
| TrafficSelector metaSelector = nextObjective.meta(); |
| if (metaSelector != null && isNotMplsBos(metaSelector)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Checks if given next objective is L2 hash next objective. |
| * |
| * @param nextObj next objective |
| * @return true if the next objective is L2 hash next objective |
| */ |
| static boolean isL2Hash(NextObjective nextObj) { |
| return nextObj.next().stream() |
| .flatMap(t -> t.allInstructions().stream()).allMatch(i -> i.type() == Instruction.Type.OUTPUT); |
| } |
| |
| /** |
| * Generates a list of group buckets from given list of group information |
| * and group bucket type. |
| * |
| * @param groupInfos a list of group information |
| * @param bucketType group bucket type |
| * @return list of group bucket generate from group information |
| */ |
| static List<GroupBucket> generateNextGroupBuckets(List<GroupInfo> groupInfos, |
| GroupDescription.Type bucketType) { |
| List<GroupBucket> newBuckets = Lists.newArrayList(); |
| |
| groupInfos.forEach(groupInfo -> { |
| GroupDescription groupDesc = groupInfo.nextGroupDesc(); |
| TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder(); |
| treatmentBuilder.group(new GroupId(groupDesc.givenGroupId())); |
| GroupBucket newBucket = null; |
| switch (bucketType) { |
| case ALL: |
| newBucket = |
| DefaultGroupBucket.createAllGroupBucket(treatmentBuilder.build()); |
| break; |
| case INDIRECT: |
| newBucket = |
| DefaultGroupBucket.createIndirectGroupBucket(treatmentBuilder.build()); |
| break; |
| case SELECT: |
| newBucket = |
| DefaultGroupBucket.createSelectGroupBucket(treatmentBuilder.build()); |
| break; |
| case FAILOVER: |
| // TODO: support failover bucket type |
| default: |
| log.warn("Unknown bucket type: {}", bucketType); |
| break; |
| } |
| |
| if (newBucket != null) { |
| newBuckets.add(newBucket); |
| } |
| |
| }); |
| |
| return ImmutableList.copyOf(newBuckets); |
| } |
| |
| static List<GroupBucket> createL3MulticastBucket(List<GroupInfo> groupInfos) { |
| List<GroupBucket> l3McastBuckets = new ArrayList<>(); |
| // For each inner group |
| groupInfos.forEach(groupInfo -> { |
| // Points to L3 interface group if there is one. |
| // Otherwise points to L2 interface group directly. |
| GroupDescription nextGroupDesc = (groupInfo.nextGroupDesc() != null) ? |
| groupInfo.nextGroupDesc() : groupInfo.innerMostGroupDesc(); |
| TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder(); |
| ttb.group(new GroupId(nextGroupDesc.givenGroupId())); |
| GroupBucket abucket = DefaultGroupBucket.createAllGroupBucket(ttb.build()); |
| l3McastBuckets.add(abucket); |
| }); |
| // Done return the new list of buckets |
| return l3McastBuckets; |
| } |
| |
| static Group retrieveTopLevelGroup(List<Deque<GroupKey>> allActiveKeys, |
| DeviceId deviceId, |
| GroupService groupService, |
| int nextid) { |
| GroupKey topLevelGroupKey; |
| if (!allActiveKeys.isEmpty()) { |
| topLevelGroupKey = allActiveKeys.get(0).peekFirst(); |
| } else { |
| log.warn("Could not determine top level group while processing" |
| + "next:{} in dev:{}", nextid, deviceId); |
| return null; |
| } |
| Group topGroup = groupService.getGroup(deviceId, topLevelGroupKey); |
| if (topGroup == null) { |
| log.warn("Could not find top level group while processing " |
| + "next:{} in dev:{}", nextid, deviceId); |
| } |
| return topGroup; |
| } |
| |
| /** |
| * Extracts VlanId from given group ID. |
| * |
| * @param groupId the group ID |
| * @return vlan id of the group |
| */ |
| public static VlanId extractVlanIdFromGroupId(int groupId) { |
| // Extract the 9th to 20th bit from group id as vlan id. |
| short vlanId = (short) ((groupId & 0x0fff0000) >> 16); |
| return VlanId.vlanId(vlanId); |
| } |
| |
| public static GroupKey l2FloodGroupKey(VlanId vlanId, DeviceId deviceId) { |
| int hash = Objects.hash(deviceId, vlanId); |
| hash = L2_FLOOD_TYPE | TYPE_MASK & hash; |
| return new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(hash)); |
| } |
| |
| public static GroupKey l2MulticastGroupKey(VlanId vlanId, DeviceId deviceId) { |
| int hash = Objects.hash(deviceId, vlanId); |
| hash = L2_MULTICAST_TYPE | TYPE_MASK & hash; |
| return new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(hash)); |
| } |
| |
| public static int l2GroupId(VlanId vlanId, long portNum) { |
| return L2_INTERFACE_TYPE | (vlanId.toShort() << 16) | (int) portNum; |
| } |
| |
| public static int l2UnfilteredGroupId(long portNum) { |
| return L2_UNFILTERED_TYPE | (int) portNum; |
| } |
| |
| /** |
| * Returns a hash as the L2 Interface Group Key. |
| * |
| * Keep the lower 6-bit for port since port number usually smaller than 64. |
| * Hash other information into remaining 22 bits. |
| * |
| * @param deviceId Device ID |
| * @param vlanId VLAN ID |
| * @param portNumber Port number |
| * @return L2 interface group key |
| */ |
| public static int l2InterfaceGroupKey(DeviceId deviceId, VlanId vlanId, long portNumber) { |
| int portLowerBits = (int) portNumber & PORT_LOWER_BITS_MASK; |
| long portHigherBits = portNumber & PORT_HIGHER_BITS_MASK; |
| int hash = Objects.hash(deviceId, vlanId, portHigherBits); |
| return L2_INTERFACE_TYPE | (TYPE_MASK & hash << 6) | portLowerBits; |
| } |
| |
| /** |
| * Returns a hash as the L2 Unfiltered Interface Group Key. |
| * |
| * Keep the lower 6-bit for port since port number usually smaller than 64. |
| * Hash other information into remaining 22 bits. |
| * |
| * @param deviceId Device ID |
| * @param portNumber Port number |
| * @return L2 unfiltered interface group key |
| */ |
| public static int l2UnfilteredGroupKey(DeviceId deviceId, long portNumber) { |
| int portLowerBits = (int) portNumber & PORT_LOWER_BITS_MASK; |
| long portHigherBits = portNumber & PORT_HIGHER_BITS_MASK; |
| int hash = Objects.hash(deviceId, portHigherBits); |
| return L2_UNFILTERED_TYPE | (TYPE_MASK & hash << 6) | portLowerBits; |
| } |
| |
| /** |
| * Returns a hash as the L2 Hash Group Key. |
| * |
| * Keep the lower 6-bit for port since port number usually smaller than 64. |
| * Hash other information into remaining 28 bits. |
| * |
| * @param deviceId Device ID |
| * @param portNumber Port number |
| * @return L2 hash group key |
| */ |
| public static int l2HashGroupKey(DeviceId deviceId, long portNumber) { |
| int portLowerBits = (int) portNumber & PORT_LOWER_BITS_MASK; |
| long portHigherBits = portNumber & PORT_HIGHER_BITS_MASK; |
| int hash = Objects.hash(deviceId, portHigherBits); |
| return L2_LB_TYPE | (TYPE_MASK & hash << 6) | portLowerBits; |
| } |
| |
| /** |
| * Utility class for moving group information around. |
| * |
| * Example: Suppose we are trying to create a group-chain A-B-C-D, where |
| * A is the top level group, and D is the inner-most group, typically L2 Interface. |
| * The innerMostGroupDesc is always D. At various stages of the creation |
| * process the nextGroupDesc may be C or B. The nextGroupDesc exists to |
| * inform the referencing group about which group it needs to point to, |
| * and wait for. In some cases the group chain may simply be A-B. In this case, |
| * both innerMostGroupDesc and nextGroupDesc will be B. |
| */ |
| public static class GroupInfo { |
| /** |
| * Description of the inner-most group of the group chain. |
| * It is always an L2 interface group. |
| */ |
| private GroupDescription innerMostGroupDesc; |
| |
| /** |
| * Description of the next group in the group chain. |
| * It can be L2 interface, L3 interface, L3 unicast, L3 VPN group. |
| * It is possible that nextGroupDesc is the same as the innerMostGroup. |
| */ |
| private GroupDescription nextGroupDesc; |
| |
| GroupInfo(GroupDescription innerMostGroupDesc, GroupDescription nextGroupDesc) { |
| this.innerMostGroupDesc = innerMostGroupDesc; |
| this.nextGroupDesc = nextGroupDesc; |
| } |
| |
| /** |
| * Getter for innerMostGroupDesc. |
| * |
| * @return the inner most group description |
| */ |
| public GroupDescription innerMostGroupDesc() { |
| return innerMostGroupDesc; |
| } |
| |
| /** |
| * Getter for the next group description. |
| * |
| * @return the next group description |
| */ |
| public GroupDescription nextGroupDesc() { |
| return nextGroupDesc; |
| } |
| |
| /** |
| * Setter of nextGroupDesc. |
| * |
| * @param nextGroupDesc the given value to set |
| */ |
| public void nextGroupDesc(GroupDescription nextGroupDesc) { |
| this.nextGroupDesc = nextGroupDesc; |
| } |
| } |
| |
| /** |
| * Represents an entire group-chain that implements a Next-Objective from |
| * the application. The objective is represented as a list of deques, where |
| * each deque is a separate chain of groups. |
| * <p> |
| * For example, an ECMP group with 3 buckets, where each bucket points to |
| * a group chain of L3 Unicast and L2 interface groups will look like this: |
| * <ul> |
| * <li>List[0] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last) |
| * <li>List[1] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last) |
| * <li>List[2] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last) |
| * </ul> |
| * where the first element of each deque is the same, representing the |
| * top level ECMP group, while every other element represents a unique groupKey. |
| * <p> |
| * Also includes information about the next objective that |
| * resulted in these group-chains. |
| * |
| */ |
| public static class OfdpaNextGroup implements NextGroup { |
| private final NextObjective nextObj; |
| private final List<Deque<GroupKey>> gkeys; |
| |
| public OfdpaNextGroup(List<Deque<GroupKey>> gkeys, NextObjective nextObj) { |
| this.nextObj = nextObj; |
| this.gkeys = gkeys; |
| } |
| |
| public NextObjective nextObjective() { |
| return nextObj; |
| } |
| |
| public List<Deque<GroupKey>> allKeys() { |
| return gkeys; |
| } |
| |
| @Override |
| public byte[] data() { |
| return Ofdpa2Pipeline.appKryo.serialize(gkeys); |
| } |
| } |
| |
| /** |
| * Represents a group element that is part of a chain of groups. |
| * Stores enough information to create a Group Description to add the group |
| * to the switch by requesting the Group Service. Objects instantiating this |
| * class are meant to be temporary and live as long as it is needed to wait for |
| * referenced groups in the group chain to be created. |
| */ |
| public static class GroupChainElem { |
| private GroupDescription groupDescription; |
| private AtomicInteger waitOnGroups; |
| private boolean addBucketToGroup; |
| private DeviceId deviceId; |
| |
| public GroupChainElem(GroupDescription groupDescription, int waitOnGroups, |
| boolean addBucketToGroup, DeviceId deviceId) { |
| this.groupDescription = groupDescription; |
| this.waitOnGroups = new AtomicInteger(waitOnGroups); |
| this.addBucketToGroup = addBucketToGroup; |
| this.deviceId = deviceId; |
| } |
| |
| /** |
| * This method atomically decrements the counter for the number of |
| * groups this GroupChainElement is waiting on, for notifications from |
| * the Group Service. When this method returns a value of 0, this |
| * GroupChainElement is ready to be processed. |
| * |
| * @return integer indication of the number of notifications being waited on |
| */ |
| int decrementAndGetGroupsWaitedOn() { |
| return waitOnGroups.decrementAndGet(); |
| } |
| |
| public GroupDescription groupDescription() { |
| return groupDescription; |
| } |
| |
| public boolean addBucketToGroup() { |
| return addBucketToGroup; |
| } |
| |
| @Override |
| public String toString() { |
| return (Integer.toHexString(groupDescription.givenGroupId()) + |
| " groupKey: " + groupDescription.appCookie() + |
| " waiting-on-groups: " + waitOnGroups.get() + |
| " addBucketToGroup: " + addBucketToGroup + |
| " device: " + deviceId); |
| } |
| } |
| |
| public static class GroupChecker implements Runnable { |
| final Logger log = getLogger(getClass()); |
| private Ofdpa2GroupHandler groupHandler; |
| |
| public GroupChecker(Ofdpa2GroupHandler groupHandler) { |
| this.groupHandler = groupHandler; |
| } |
| |
| @Override |
| public void run() { |
| // GroupChecker execution needs to be protected |
| // from unhandled exceptions |
| try { |
| if (groupHandler.pendingGroups.size() != 0) { |
| log.debug("pending groups being checked: {}", |
| groupHandler.pendingGroups.asMap().keySet()); |
| } |
| if (groupHandler.pendingAddNextObjectives.size() != 0) { |
| log.debug("pending add-next-obj being checked: {}", |
| groupHandler.pendingAddNextObjectives.asMap().keySet()); |
| } |
| if (groupHandler.pendingRemoveNextObjectives.size() != 0) { |
| log.debug("pending remove-next-obj being checked: {}", |
| groupHandler.pendingRemoveNextObjectives.asMap().values()); |
| } |
| if (groupHandler.pendingUpdateNextObjectives.size() != 0) { |
| log.debug("pending update-next-obj being checked: {}", |
| groupHandler.pendingUpdateNextObjectives.keySet()); |
| } |
| |
| Set<GroupKey> keys = groupHandler.pendingGroups.asMap().keySet() |
| .stream() |
| .filter(key -> groupHandler.groupService |
| .getGroup(groupHandler.deviceId, key) != null) |
| .collect(Collectors.toSet()); |
| Set<GroupKey> otherkeys = groupHandler.pendingAddNextObjectives |
| .asMap().keySet().stream() |
| .filter(otherkey -> groupHandler.groupService |
| .getGroup(groupHandler.deviceId, otherkey) != null) |
| .collect(Collectors.toSet()); |
| keys.addAll(otherkeys); |
| keys.forEach(key -> groupHandler.processPendingAddGroupsOrNextObjs(key, false)); |
| |
| keys = groupHandler.pendingUpdateNextObjectives.keySet() |
| .stream() |
| .filter(key -> groupHandler.groupService |
| .getGroup(groupHandler.deviceId, key) != null) |
| .collect(Collectors.toSet()); |
| keys.forEach(key -> groupHandler.processPendingUpdateNextObjs(key)); |
| |
| Set<GroupKey> k = Sets.newHashSet(); |
| groupHandler.pendingRemoveNextObjectives.asMap().values().forEach(keylist -> keylist.stream() |
| .filter(key -> groupHandler.groupService.getGroup(groupHandler.deviceId, key) == null) |
| .forEach(k::add)); |
| k.forEach(key -> groupHandler.processPendingRemoveNextObjs(key)); |
| |
| } catch (Exception exception) { |
| // Just log. It is safe for now. |
| log.warn("Uncaught exception is detected: {}", exception.getMessage()); |
| log.debug("Uncaught exception is detected (full stack trace): ", exception); |
| } |
| } |
| } |
| |
| /** |
| * Helper method to decide whether L2 Interface group or L2 Unfiltered group needs to be created. |
| * L2 Unfiltered group will be created if meta has VlanIdCriterion with VlanId.ANY, and |
| * treatment has set Vlan ID action. |
| * |
| * @param treatment treatment passed in by the application as part of the nextObjective |
| * @param meta metadata passed in by the application as part of the nextObjective |
| * @return true if L2 Unfiltered group needs to be created, false otherwise. |
| */ |
| public static boolean createUnfiltered(TrafficTreatment treatment, TrafficSelector meta) { |
| if (meta == null || treatment == null) { |
| return false; |
| } |
| VlanIdCriterion vlanIdCriterion = (VlanIdCriterion) meta.getCriterion(Criterion.Type.VLAN_VID); |
| if (vlanIdCriterion == null || !vlanIdCriterion.vlanId().equals(VlanId.ANY)) { |
| return false; |
| } |
| |
| return treatment.allInstructions().stream() |
| .filter(i -> (i.type() == Instruction.Type.L2MODIFICATION |
| && ((L2ModificationInstruction) i).subtype() == L2ModificationInstruction.L2SubType.VLAN_ID)) |
| .count() == 1; |
| } |
| |
| /** |
| * Returns a hash as the L3 Unicast Group Key. |
| * |
| * Keep the lower 6-bit for port since port number usually smaller than 64. |
| * Hash other information into remaining 28 bits. |
| * |
| * @param deviceId Device ID |
| * @param vlanId vlan ID |
| * @param portNumber Port number |
| * @return L3 unicast group key |
| */ |
| public static int doubleVlanL3UnicastGroupKey(DeviceId deviceId, VlanId vlanId, long portNumber) { |
| int portLowerBits = (int) portNumber & PORT_LOWER_BITS_MASK; |
| long portHigherBits = portNumber & PORT_HIGHER_BITS_MASK; |
| int hash = Objects.hash(deviceId, portHigherBits, vlanId); |
| return L3_UNICAST_TYPE | (TYPE_MASK & hash << 6) | portLowerBits; |
| } |
| |
| public static int doubleVlanL3UnicastGroupId(VlanId vlanId, long portNum) { |
| // <4bits-2><1bit-1><12bits-vlanId><15bits-portId> |
| return L3_UNICAST_TYPE | 1 << 27 | (vlanId.toShort() << 15) | (int) (portNum & 0x7FFF); |
| } |
| |
| /** |
| * Helper method to decide whether L2 Interface group or L2 Unfiltered group needs to be created. |
| * L2 Unfiltered group will be created if meta has VlanIdCriterion with VlanId.ANY, and |
| * treatment has set Vlan ID action. |
| * |
| * @param treatment treatment passed in by the application as part of the nextObjective |
| * @param meta metadata passed in by the application as part of the nextObjective |
| * @return true if L2 Unfiltered group needs to be created, false otherwise. |
| */ |
| static boolean isUnfiltered(TrafficTreatment treatment, TrafficSelector meta) { |
| if (meta == null || treatment == null) { |
| return false; |
| } |
| VlanIdCriterion vlanIdCriterion = (VlanIdCriterion) meta.getCriterion(Criterion.Type.VLAN_VID); |
| if (vlanIdCriterion == null || !vlanIdCriterion.vlanId().equals(VlanId.ANY)) { |
| return false; |
| } |
| |
| return treatment.allInstructions().stream() |
| .filter(i -> (i.type() == Instruction.Type.L2MODIFICATION |
| && ((L2ModificationInstruction) i).subtype() == L2ModificationInstruction.L2SubType.VLAN_ID)) |
| .count() == 1; |
| } |
| |
| } |