[CORD-2721] Implement group bucket modification
Change-Id: I0f637ec4ff2b0c12db53d70fed195ea28e542535
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 b9912ac..f72d11b 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 @@
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 @@
}
}
+ 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 @@
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 @@
} 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 75cab32..3cf1331 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 @@
bc.run();
}
- public void updateGroupFromVlanConfiguration(PortNumber portNumber, Collection<VlanId> 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 671cc5f..8c466c9 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
@@ -316,6 +316,24 @@
}
@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 725ebf5..4cc8478 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
@@ -228,6 +228,23 @@
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.
*
* @return a next objective with {@link Operation} VERIFY
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 f0e4305..3b0346c 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
@@ -63,6 +63,11 @@
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.
* Corrective action is taken if discrepancies are found during verification.
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 e41ca4c..fdee8d6 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 @@
});
}
- private 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;
- }
-
-
private void createL3MulticastGroup(NextObjective nextObj, VlanId vlanId,
List<GroupInfo> groupInfos) {
// Let's create a new list mcast buckets
@@ -1197,7 +1179,8 @@
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 @@
List<Deque<GroupKey>> allActiveKeys,
List<GroupInfo> 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 @@
List<GroupBucket> 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;
@@ -1579,6 +1564,56 @@
}
/**
+ * 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<GroupInfo> 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<Deque<GroupKey>> modifiedGroupKeys = Lists.newArrayList();
+ ArrayDeque<GroupKey> 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
* to ensure that the buckets match up.
@@ -1840,24 +1875,6 @@
return (int) nextIndex.incrementAndGet();
}
- protected Group retrieveTopLevelGroup(List<Deque<GroupKey>> 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<OfdpaGroupHandlerUtility.GroupChainElem> 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 661d3e9..b481c3f 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 @@
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 e9c97bc..21802ea 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 @@
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.
*