[CORD-2721] Implement group bucket modification

Change-Id: I0f637ec4ff2b0c12db53d70fed195ea28e542535
This commit is contained in:
Jonghwan Hyun 2018-02-12 16:43:45 +09:00
parent 931d3e7a4a
commit f810a7a2e7
8 changed files with 265 additions and 64 deletions

View File

@ -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));

View File

@ -1277,12 +1277,56 @@ public class DefaultGroupHandler {
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();

View File

@ -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);

View File

@ -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.
*

View File

@ -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.

View File

@ -804,24 +804,6 @@ public class Ofdpa2GroupHandler {
});
}
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 @@ 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<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 @@ public class Ofdpa2GroupHandler {
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;
@ -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<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
@ -1840,24 +1875,6 @@ public class Ofdpa2GroupHandler {
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);

View File

@ -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 {}",

View File

@ -394,6 +394,43 @@ public final class OfdpaGroupHandlerUtility {
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.
*