From f810a7a2e76f69acecb7a8aece9d5efb7accd2b5 Mon Sep 17 00:00:00 2001 From: Jonghwan Hyun Date: Mon, 12 Feb 2018 16:43:45 +0900 Subject: [PATCH] [CORD-2721] Implement group bucket modification Change-Id: I0f637ec4ff2b0c12db53d70fed195ea28e542535 --- .../segmentrouting/SegmentRoutingManager.java | 95 +++++++++++++++---- .../grouphandler/DefaultGroupHandler.java | 52 +++++++++- .../flowobjective/DefaultNextObjective.java | 18 ++++ .../net/flowobjective/NextObjective.java | 17 ++++ .../net/flowobjective/Objective.java | 5 + .../pipeline/ofdpa/Ofdpa2GroupHandler.java | 95 +++++++++++-------- .../driver/pipeline/ofdpa/Ofdpa2Pipeline.java | 10 ++ .../ofdpa/OfdpaGroupHandlerUtility.java | 37 ++++++++ 8 files changed, 265 insertions(+), 64 deletions(-) diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java index b9912accad..f72d11b449 100644 --- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java +++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java @@ -1568,38 +1568,71 @@ public class SegmentRoutingManager implements SegmentRoutingService { Sets.difference(new HashSet<>(prevIntf.ipAddressesList()), new HashSet<>(intf.ipAddressesList()))); - if (prevIntf.vlanNative() != VlanId.NONE && !intf.vlanNative().equals(prevIntf.vlanNative())) { - // RemoveVlanNative - updateVlanConfigInternal(deviceId, portNum, prevIntf.vlanNative(), true, false); + if (!prevIntf.vlanNative().equals(VlanId.NONE) + && !prevIntf.vlanNative().equals(intf.vlanUntagged()) + && !prevIntf.vlanNative().equals(intf.vlanNative())) { + if (intf.vlanTagged().contains(prevIntf.vlanNative())) { + // Update filtering objective and L2IG group bucket + updatePortVlanTreatment(deviceId, portNum, prevIntf.vlanNative(), false); + } else { + // RemoveVlanNative + updateVlanConfigInternal(deviceId, portNum, prevIntf.vlanNative(), true, false); + } + } + + if (!prevIntf.vlanUntagged().equals(VlanId.NONE) + && !prevIntf.vlanUntagged().equals(intf.vlanUntagged()) + && !prevIntf.vlanUntagged().equals(intf.vlanNative())) { + if (intf.vlanTagged().contains(prevIntf.vlanUntagged())) { + // Update filtering objective and L2IG group bucket + updatePortVlanTreatment(deviceId, portNum, prevIntf.vlanUntagged(), false); + } else { + // RemoveVlanUntagged + updateVlanConfigInternal(deviceId, portNum, prevIntf.vlanUntagged(), true, false); + } } if (!prevIntf.vlanTagged().isEmpty() && !intf.vlanTagged().equals(prevIntf.vlanTagged())) { // RemoveVlanTagged - prevIntf.vlanTagged().stream().filter(i -> !intf.vlanTagged().contains(i)).forEach( - vlanId -> updateVlanConfigInternal(deviceId, portNum, vlanId, false, false) - ); + Sets.difference(prevIntf.vlanTagged(), intf.vlanTagged()).stream() + .filter(i -> !intf.vlanUntagged().equals(i)) + .filter(i -> !intf.vlanNative().equals(i)) + .forEach(vlanId -> updateVlanConfigInternal( + deviceId, portNum, vlanId, false, false)); } - if (prevIntf.vlanUntagged() != VlanId.NONE && !intf.vlanUntagged().equals(prevIntf.vlanUntagged())) { - // RemoveVlanUntagged - updateVlanConfigInternal(deviceId, portNum, prevIntf.vlanUntagged(), true, false); - } - - if (intf.vlanNative() != VlanId.NONE && !prevIntf.vlanNative().equals(intf.vlanNative())) { - // AddVlanNative - updateVlanConfigInternal(deviceId, portNum, intf.vlanNative(), true, true); + if (!intf.vlanNative().equals(VlanId.NONE) + && !prevIntf.vlanNative().equals(intf.vlanNative()) + && !prevIntf.vlanUntagged().equals(intf.vlanNative())) { + if (prevIntf.vlanTagged().contains(intf.vlanNative())) { + // Update filtering objective and L2IG group bucket + updatePortVlanTreatment(deviceId, portNum, intf.vlanNative(), true); + } else { + // AddVlanNative + updateVlanConfigInternal(deviceId, portNum, intf.vlanNative(), true, true); + } } if (!intf.vlanTagged().isEmpty() && !intf.vlanTagged().equals(prevIntf.vlanTagged())) { // AddVlanTagged - intf.vlanTagged().stream().filter(i -> !prevIntf.vlanTagged().contains(i)).forEach( - vlanId -> updateVlanConfigInternal(deviceId, portNum, vlanId, false, true) + Sets.difference(intf.vlanTagged(), prevIntf.vlanTagged()).stream() + .filter(i -> !prevIntf.vlanUntagged().equals(i)) + .filter(i -> !prevIntf.vlanNative().equals(i)) + .forEach(vlanId -> updateVlanConfigInternal( + deviceId, portNum, vlanId, false, true) ); } - if (intf.vlanUntagged() != VlanId.NONE && !prevIntf.vlanUntagged().equals(intf.vlanUntagged())) { - // AddVlanUntagged - updateVlanConfigInternal(deviceId, portNum, intf.vlanUntagged(), true, true); + if (!intf.vlanUntagged().equals(VlanId.NONE) + && !prevIntf.vlanUntagged().equals(intf.vlanUntagged()) + && !prevIntf.vlanNative().equals(intf.vlanUntagged())) { + if (prevIntf.vlanTagged().contains(intf.vlanUntagged())) { + // Update filtering objective and L2IG group bucket + updatePortVlanTreatment(deviceId, portNum, intf.vlanUntagged(), true); + } else { + // AddVlanUntagged + updateVlanConfigInternal(deviceId, portNum, intf.vlanUntagged(), true, true); + } } addSubnetConfig(prevIntf.connectPoint(), Sets.difference(new HashSet<>(intf.ipAddressesList()), @@ -1609,6 +1642,26 @@ public class SegmentRoutingManager implements SegmentRoutingService { } } + private void updatePortVlanTreatment(DeviceId deviceId, PortNumber portNum, + VlanId vlanId, boolean pushVlan) { + DefaultGroupHandler grpHandler = getGroupHandler(deviceId); + if (grpHandler == null) { + log.warn("Failed to retrieve group handler for device {}", deviceId); + return; + } + + // Update filtering objective for a single port + routingRulePopulator.updateSinglePortFilters(deviceId, portNum, !pushVlan, vlanId, false); + routingRulePopulator.updateSinglePortFilters(deviceId, portNum, pushVlan, vlanId, true); + + if (getVlanNextObjectiveId(deviceId, vlanId) != -1) { + // Update L2IG bucket of the port + grpHandler.updateL2InterfaceGroupBucket(portNum, vlanId, pushVlan); + } else { + log.warn("Failed to retrieve next objective for vlan {} in device {}:{}", vlanId, deviceId, portNum); + } + } + private void updateVlanConfigInternal(DeviceId deviceId, PortNumber portNum, VlanId vlanId, boolean pushVlan, boolean install) { DefaultGroupHandler grpHandler = getGroupHandler(deviceId); @@ -1628,7 +1681,7 @@ public class SegmentRoutingManager implements SegmentRoutingService { if (nextId != -1 && !install) { // Update next objective for a single port as an output port // Remove a single port from L2FG - grpHandler.updateGroupFromVlanConfiguration(portNum, Collections.singleton(vlanId), nextId, install); + grpHandler.updateGroupFromVlanConfiguration(vlanId, portNum, nextId, install); // Remove L2 Bridging rule and L3 Unicast rule to the host hostHandler.processIntfVlanUpdatedEvent(deviceId, portNum, vlanId, pushVlan, install); // Remove broadcast forwarding rule and corresponding L2FG for VLAN @@ -1645,7 +1698,7 @@ public class SegmentRoutingManager implements SegmentRoutingService { } else if (install) { if (nextId != -1) { // Add a single port to L2FG - grpHandler.updateGroupFromVlanConfiguration(portNum, Collections.singleton(vlanId), nextId, install); + grpHandler.updateGroupFromVlanConfiguration(vlanId, portNum, nextId, install); } else { // Create L2FG for VLAN grpHandler.createBcastGroupFromVlan(vlanId, Collections.singleton(portNum)); diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java index 75cab32be5..3cf13316bb 100644 --- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java +++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java @@ -1277,12 +1277,56 @@ public class DefaultGroupHandler { bc.run(); } - public void updateGroupFromVlanConfiguration(PortNumber portNumber, Collection vlanIds, - int nextId, boolean install) { - vlanIds.forEach(vlanId -> updateGroupFromVlanInternal(vlanId, portNumber, nextId, install)); + /** + * Modifies L2IG bucket when the interface configuration is updated, especially + * when the interface has same VLAN ID but the VLAN type is changed (e.g., from + * vlan-tagged [10] to vlan-untagged 10), which requires changes on + * TrafficTreatment in turn. + * + * @param portNumber the port on this device that needs to be updated + * @param vlanId the vlan id corresponding to this port + * @param pushVlan indicates if packets should be sent out untagged or not out + * from the port. If true, updated TrafficTreatment involves + * pop vlan tag action. If false, updated TrafficTreatment + * does not involve pop vlan tag action. + */ + public void updateL2InterfaceGroupBucket(PortNumber portNumber, VlanId vlanId, boolean pushVlan) { + TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder(); + if (pushVlan) { + tBuilder.popVlan(); + } + tBuilder.setOutput(portNumber); + + TrafficSelector metadata = + DefaultTrafficSelector.builder().matchVlanId(vlanId).build(); + + int nextId = getVlanNextObjectiveId(vlanId); + + NextObjective.Builder nextObjBuilder = DefaultNextObjective + .builder().withId(nextId) + .withType(NextObjective.Type.SIMPLE).fromApp(appId) + .addTreatment(tBuilder.build()) + .withMeta(metadata); + + ObjectiveContext context = new DefaultObjectiveContext( + (objective) -> log.debug("port {} successfully updated NextObj {} on {}", + portNumber, nextId, deviceId), + (objective, error) -> + log.warn("port {} failed to updated NextObj {} on {}: {}", + portNumber, nextId, deviceId, error)); + + flowObjectiveService.next(deviceId, nextObjBuilder.modify(context)); } - private void updateGroupFromVlanInternal(VlanId vlanId, PortNumber portNum, int nextId, boolean install) { + /** + * Adds a single port to the L2FG or removes it from the L2FG. + * + * @param vlanId the vlan id corresponding to this port + * @param portNum the port on this device to be updated + * @param nextId the next objective ID for the given vlan id + * @param install if true, adds the port to L2FG. If false, removes it from L2FG. + */ + public void updateGroupFromVlanConfiguration(VlanId vlanId, PortNumber portNum, int nextId, boolean install) { TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder(); if (toPopVlan(portNum, vlanId)) { tBuilder.popVlan(); diff --git a/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultNextObjective.java b/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultNextObjective.java index 671cc5f7e6..8c466c9524 100644 --- a/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultNextObjective.java +++ b/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultNextObjective.java @@ -315,6 +315,24 @@ public final class DefaultNextObjective implements NextObjective { return new DefaultNextObjective(this); } + @Override + public NextObjective modify() { + return modify(null); + } + + @Override + public NextObjective modify(ObjectiveContext context) { + treatments = listBuilder.build(); + op = Operation.MODIFY; + this.context = context; + checkNotNull(appId, "Must supply an application id"); + checkNotNull(id, "id cannot be null"); + checkNotNull(type, "The type cannot be null"); + checkArgument(!treatments.isEmpty(), "Must have at least one treatment"); + + return new DefaultNextObjective(this); + } + @Override public NextObjective verify() { return verify(null); diff --git a/core/api/src/main/java/org/onosproject/net/flowobjective/NextObjective.java b/core/api/src/main/java/org/onosproject/net/flowobjective/NextObjective.java index 725ebf5f74..4cc8478d39 100644 --- a/core/api/src/main/java/org/onosproject/net/flowobjective/NextObjective.java +++ b/core/api/src/main/java/org/onosproject/net/flowobjective/NextObjective.java @@ -227,6 +227,23 @@ public interface NextObjective extends Objective { */ NextObjective removeFromExisting(ObjectiveContext context); + /** + * Build the next objective that will be modified with {@link Operation} + * MODIFY. + * + * @return a next objective + */ + + NextObjective modify(); + /** + * Build the next objective that will be modified, with {@link Operation} + * MODIFY. The context will be used to notify the calling application. + * + * @param context an objective context + * @return a next objective + */ + NextObjective modify(ObjectiveContext context); + /** * Builds the next objective that needs to be verified. * diff --git a/core/api/src/main/java/org/onosproject/net/flowobjective/Objective.java b/core/api/src/main/java/org/onosproject/net/flowobjective/Objective.java index f0e43056d2..3b0346c8fa 100644 --- a/core/api/src/main/java/org/onosproject/net/flowobjective/Objective.java +++ b/core/api/src/main/java/org/onosproject/net/flowobjective/Objective.java @@ -62,6 +62,11 @@ public interface Objective { */ REMOVE_FROM_EXISTING, + /** + * Modify an existing Next Objective. Can be used to modify group buckets. + */ + MODIFY, + /** * Verifies that an existing Next Objective's collection of treatments * are correctly represented by the underlying implementation of the objective. diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java index e41ca4c327..fdee8d6dea 100644 --- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java +++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java @@ -804,24 +804,6 @@ public class Ofdpa2GroupHandler { }); } - private List createL3MulticastBucket(List groupInfos) { - List 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; - } - - private void createL3MulticastGroup(NextObjective nextObj, VlanId vlanId, List groupInfos) { // Let's create a new list mcast buckets @@ -1197,7 +1179,8 @@ public class Ofdpa2GroupHandler { newBuckets = generateNextGroupBuckets(unsentGroups, SELECT); // retrieve the original L3 ECMP group - Group l3ecmpGroup = retrieveTopLevelGroup(allActiveKeys, nextObjective.id()); + Group l3ecmpGroup = retrieveTopLevelGroup(allActiveKeys, deviceId, + groupService, nextObjective.id()); if (l3ecmpGroup == null) { fail(nextObjective, ObjectiveError.GROUPMISSING); return; @@ -1270,7 +1253,8 @@ public class Ofdpa2GroupHandler { List> allActiveKeys, List groupInfos, VlanId assignedVlan) { - Group l2FloodGroup = retrieveTopLevelGroup(allActiveKeys, nextObj.id()); + Group l2FloodGroup = retrieveTopLevelGroup(allActiveKeys, deviceId, + groupService, nextObj.id()); if (l2FloodGroup == null) { log.warn("Can't find L2 flood group while adding bucket to it. NextObj = {}", @@ -1350,7 +1334,8 @@ public class Ofdpa2GroupHandler { List newBuckets = createL3MulticastBucket(groupInfos); // get the group being edited - Group l3mcastGroup = retrieveTopLevelGroup(allActiveKeys, nextObj.id()); + Group l3mcastGroup = retrieveTopLevelGroup(allActiveKeys, deviceId, + groupService, nextObj.id()); if (l3mcastGroup == null) { fail(nextObj, ObjectiveError.GROUPMISSING); return; @@ -1578,6 +1563,56 @@ public class Ofdpa2GroupHandler { flowObjectiveStore.removeNextGroup(nextObjective.id()); } + /** + * Modify buckets in the L2 interface group. + * + * @param nextObjective a next objective that contains information for the + * buckets to be modified in the group + * @param next the representation of the existing group-chains for this next + * objective, from which the innermost group buckets to remove are determined + */ + protected void modifyBucketFromGroup(NextObjective nextObjective, NextGroup next) { + if (nextObjective.type() != NextObjective.Type.SIMPLE) { + log.warn("ModifyBucketFromGroup cannot be applied to nextType:{} in dev:{} for next:{}", + nextObjective.type(), deviceId, nextObjective.id()); + fail(nextObjective, ObjectiveError.UNSUPPORTED); + return; + } + + VlanId assignedVlan = readVlanFromSelector(nextObjective.meta()); + if (assignedVlan == null) { + log.warn("VLAN ID required by simple next obj is missing. Abort."); + fail(nextObjective, ObjectiveError.BADPARAMS); + return; + } + + List groupInfos = prepareL2InterfaceGroup(nextObjective, assignedVlan); + + // There is only one L2 interface group in this case + GroupDescription l2InterfaceGroupDesc = groupInfos.get(0).innerMostGroupDesc(); + + // Replace group bucket for L2 interface group + groupService.setBucketsForGroup(deviceId, + l2InterfaceGroupDesc.appCookie(), + l2InterfaceGroupDesc.buckets(), + l2InterfaceGroupDesc.appCookie(), + l2InterfaceGroupDesc.appId()); + + // update store - synchronize access as there may be multiple threads + // trying to remove buckets from the same group, each with its own + // potentially stale copy of allActiveKeys + synchronized (flowObjectiveStore) { + List> modifiedGroupKeys = Lists.newArrayList(); + ArrayDeque top = new ArrayDeque<>(); + top.add(l2InterfaceGroupDesc.appCookie()); + modifiedGroupKeys.add(top); + + flowObjectiveStore.putNextGroup(nextObjective.id(), + new OfdpaNextGroup(modifiedGroupKeys, + nextObjective)); + } + } + /** * Checks existing buckets in {@link NextGroup} to verify if they match * the buckets in the given {@link NextObjective}. Adds or removes buckets @@ -1840,24 +1875,6 @@ public class Ofdpa2GroupHandler { return (int) nextIndex.incrementAndGet(); } - protected Group retrieveTopLevelGroup(List> allActiveKeys, - 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; - } - protected void processPendingAddGroupsOrNextObjs(GroupKey key, boolean added) { //first check for group chain Set gceSet = pendingGroups.asMap().remove(key); diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java index 661d3e99ec..b481c3fe20 100644 --- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java +++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java @@ -381,6 +381,16 @@ public class Ofdpa2Pipeline extends AbstractHandlerBehaviour implements Pipeline nextObjective.id(), deviceId); groupHandler.removeBucketFromGroup(nextObjective, nextGroup); break; + case MODIFY: + if (nextGroup == null) { + log.warn("Cannot modify next {} that does not exist in device {}", + nextObjective.id(), deviceId); + return; + } + log.debug("Processing NextObjective id {} in dev {} - modify bucket", + nextObjective.id(), deviceId); + groupHandler.modifyBucketFromGroup(nextObjective, nextGroup); + break; case VERIFY: if (nextGroup == null) { log.warn("Cannot verify next {} that does not exist in device {}", diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/OfdpaGroupHandlerUtility.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/OfdpaGroupHandlerUtility.java index e9c97bc61f..21802ea537 100644 --- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/OfdpaGroupHandlerUtility.java +++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/OfdpaGroupHandlerUtility.java @@ -394,6 +394,43 @@ public final class OfdpaGroupHandlerUtility { return ImmutableList.copyOf(newBuckets); } + static List createL3MulticastBucket(List groupInfos) { + List 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> 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. *