From 45c27c8c62a4dd385227d7af03c685de11f6768b Mon Sep 17 00:00:00 2001 From: Srikanth Vavilapalli Date: Fri, 30 Jan 2015 12:57:56 -0800 Subject: [PATCH] ONOS-895: Group manager implementation Change-Id: Ie183f722fa39012f8de056961715c325e2388e63 --- .../org/onosproject/core/DefaultGroupId.java | 2 +- .../onosproject/net/group/DefaultGroup.java | 66 +++ .../net/group/DefaultGroupBucket.java | 34 ++ .../net/group/DefaultGroupDescription.java | 64 ++- .../java/org/onosproject/net/group/Group.java | 11 + .../onosproject/net/group/GroupBuckets.java | 21 + .../onosproject/net/group/GroupOperation.java | 35 ++ .../net/group/GroupProviderRegistry.java | 25 ++ .../onosproject/net/group/GroupService.java | 17 +- .../org/onosproject/net/group/GroupStore.java | 45 +- .../net/group/StoredGroupEntry.java | 6 + .../net/group/impl/GroupManager.java | 366 ++++++++++++++++ .../net/group/impl/package-info.java | 20 + .../net/group/impl/GroupManagerTest.java | 400 ++++++++++++++++++ .../store/trivial/impl/SimpleGroupStore.java | 207 +++++++-- .../trivial/impl/SimpleGroupStoreTest.java | 71 ++-- 16 files changed, 1300 insertions(+), 90 deletions(-) create mode 100644 core/api/src/main/java/org/onosproject/net/group/GroupProviderRegistry.java create mode 100644 core/net/src/main/java/org/onosproject/net/group/impl/GroupManager.java create mode 100644 core/net/src/main/java/org/onosproject/net/group/impl/package-info.java create mode 100644 core/net/src/test/java/org/onosproject/net/group/impl/GroupManagerTest.java diff --git a/core/api/src/main/java/org/onosproject/core/DefaultGroupId.java b/core/api/src/main/java/org/onosproject/core/DefaultGroupId.java index 11ca73caf9..58ae9a9128 100644 --- a/core/api/src/main/java/org/onosproject/core/DefaultGroupId.java +++ b/core/api/src/main/java/org/onosproject/core/DefaultGroupId.java @@ -37,7 +37,7 @@ public class DefaultGroupId implements GroupId { @Override public int id() { - return 0; + return this.id; } @Override diff --git a/core/api/src/main/java/org/onosproject/net/group/DefaultGroup.java b/core/api/src/main/java/org/onosproject/net/group/DefaultGroup.java index 0e4cac1371..eba2c6c987 100644 --- a/core/api/src/main/java/org/onosproject/net/group/DefaultGroup.java +++ b/core/api/src/main/java/org/onosproject/net/group/DefaultGroup.java @@ -17,7 +17,10 @@ package org.onosproject.net.group; import static org.slf4j.LoggerFactory.getLogger; +import java.util.Objects; + import org.onosproject.core.GroupId; +import org.onosproject.net.DeviceId; import org.slf4j.Logger; /** @@ -32,6 +35,7 @@ public class DefaultGroup extends DefaultGroupDescription private long life; private long packets; private long bytes; + private long referenceCount; private GroupId id; /** @@ -47,6 +51,29 @@ public class DefaultGroup extends DefaultGroupDescription this.life = 0; this.packets = 0; this.bytes = 0; + this.referenceCount = 0; + } + + /** + * Default group object constructor with the available information + * from data plane. + * + * @param id group identifier + * @param deviceId device identifier + * @param type type of the group + * @param buckets immutable list of group bucket + */ + public DefaultGroup(GroupId id, + DeviceId deviceId, + GroupDescription.Type type, + GroupBuckets buckets) { + super(deviceId, type, buckets); + this.id = id; + this.state = GroupState.PENDING_ADD; + this.life = 0; + this.packets = 0; + this.bytes = 0; + this.referenceCount = 0; } /** @@ -139,4 +166,43 @@ public class DefaultGroup extends DefaultGroupDescription this.bytes = bytes; } + @Override + public void setReferenceCount(long referenceCount) { + this.referenceCount = referenceCount; + } + + @Override + public long referenceCount() { + return referenceCount; + } + + /* + * The deviceId, type and buckets are used for hash. + * + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public int hashCode() { + return super.hashCode() + Objects.hash(id); + } + + /* + * The deviceId, groupId, type and buckets should be same. + * + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof DefaultGroup) { + DefaultGroup that = (DefaultGroup) obj; + return super.equals(obj) && + Objects.equals(id, that.id); + } + return false; + } } diff --git a/core/api/src/main/java/org/onosproject/net/group/DefaultGroupBucket.java b/core/api/src/main/java/org/onosproject/net/group/DefaultGroupBucket.java index 3fab387033..931cc71cfc 100644 --- a/core/api/src/main/java/org/onosproject/net/group/DefaultGroupBucket.java +++ b/core/api/src/main/java/org/onosproject/net/group/DefaultGroupBucket.java @@ -18,6 +18,8 @@ package org.onosproject.net.group; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import java.util.Objects; + import org.onosproject.core.GroupId; import org.onosproject.net.PortNumber; import org.onosproject.net.flow.TrafficTreatment; @@ -178,4 +180,36 @@ public final class DefaultGroupBucket implements GroupBucket { public GroupId watchGroup() { return watchGroup; } + + /* + * The type and treatment can change on a given bucket + * + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public int hashCode() { + return Objects.hash(type, treatment); + } + + /* + * The priority and statistics can change on a given treatment and selector + * + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof DefaultGroupBucket) { + DefaultGroupBucket that = (DefaultGroupBucket) obj; + return Objects.equals(type, that.type) && + this.treatment.instructions().containsAll(that.treatment.instructions()) && + that.treatment.instructions().containsAll(this.treatment.instructions()); + } + return false; + } + } diff --git a/core/api/src/main/java/org/onosproject/net/group/DefaultGroupDescription.java b/core/api/src/main/java/org/onosproject/net/group/DefaultGroupDescription.java index 25af506e18..8d374c1b98 100644 --- a/core/api/src/main/java/org/onosproject/net/group/DefaultGroupDescription.java +++ b/core/api/src/main/java/org/onosproject/net/group/DefaultGroupDescription.java @@ -17,6 +17,8 @@ package org.onosproject.net.group; import static com.google.common.base.Preconditions.checkNotNull; +import java.util.Objects; + import org.onosproject.core.ApplicationId; import org.onosproject.net.DeviceId; @@ -49,8 +51,8 @@ public class DefaultGroupDescription implements GroupDescription { this.type = checkNotNull(type); this.deviceId = checkNotNull(deviceId); this.buckets = checkNotNull(buckets); - this.appCookie = checkNotNull(appCookie); - this.appId = checkNotNull(appId); + this.appCookie = appCookie; + this.appId = appId; } /** @@ -61,11 +63,27 @@ public class DefaultGroupDescription implements GroupDescription { * */ public DefaultGroupDescription(GroupDescription groupDesc) { - this.type = checkNotNull(groupDesc.type()); - this.deviceId = checkNotNull(groupDesc.deviceId()); - this.buckets = checkNotNull(groupDesc.buckets()); - this.appCookie = checkNotNull(groupDesc.appCookie()); - this.appId = checkNotNull(groupDesc.appId()); + this.type = groupDesc.type(); + this.deviceId = groupDesc.deviceId(); + this.buckets = groupDesc.buckets(); + this.appCookie = groupDesc.appCookie(); + this.appId = groupDesc.appId(); + } + + /** + * Constructor to be used by group subsystem internal components. + * Creates group description object from the information retrieved + * from data plane. + * + * @param deviceId device identifier + * @param type type of the group + * @param buckets immutable list of group bucket + * + */ + public DefaultGroupDescription(DeviceId deviceId, + GroupDescription.Type type, + GroupBuckets buckets) { + this(deviceId, type, buckets, null, null); } /** @@ -118,4 +136,36 @@ public class DefaultGroupDescription implements GroupDescription { return this.buckets; } + @Override + /* + * The deviceId, type and buckets are used for hash. + * + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + public int hashCode() { + return Objects.hash(deviceId, type, buckets); + } + + @Override + /* + * The deviceId, type and buckets should be same. + * + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof DefaultGroupDescription) { + DefaultGroupDescription that = (DefaultGroupDescription) obj; + return Objects.equals(deviceId, that.deviceId) && + Objects.equals(type, that.type) && + Objects.equals(buckets, that.buckets); + + } + return false; + } + } \ No newline at end of file diff --git a/core/api/src/main/java/org/onosproject/net/group/Group.java b/core/api/src/main/java/org/onosproject/net/group/Group.java index f7fa507879..b7872de74d 100644 --- a/core/api/src/main/java/org/onosproject/net/group/Group.java +++ b/core/api/src/main/java/org/onosproject/net/group/Group.java @@ -25,6 +25,10 @@ public interface Group extends GroupDescription { * State of the group object in ONOS. */ public enum GroupState { + /** + * Group create request is queued as group AUDIT is in progress. + */ + WAITING_AUDIT_COMPLETE, /** * Group create request is processed by ONOS and not yet * received the confirmation from data plane. @@ -81,4 +85,11 @@ public interface Group extends GroupDescription { * @return number of bytes */ long bytes(); + + /** + * Returns the number of flow rules or other groups reference this group. + * + * @return number of flow rules or other groups pointing to this group + */ + long referenceCount(); } diff --git a/core/api/src/main/java/org/onosproject/net/group/GroupBuckets.java b/core/api/src/main/java/org/onosproject/net/group/GroupBuckets.java index 10f4ecab81..5ca8f3084e 100644 --- a/core/api/src/main/java/org/onosproject/net/group/GroupBuckets.java +++ b/core/api/src/main/java/org/onosproject/net/group/GroupBuckets.java @@ -45,4 +45,25 @@ public final class GroupBuckets { return buckets; } + @Override + public int hashCode() { + int result = 17; + int combinedHash = 0; + for (GroupBucket bucket:buckets) { + combinedHash = combinedHash + bucket.hashCode(); + } + result = 31 * result + combinedHash; + + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof GroupBuckets) { + return (this.buckets.containsAll(((GroupBuckets) obj).buckets) && + ((GroupBuckets) obj).buckets.containsAll(this.buckets)); + } + return false; + } + } \ No newline at end of file diff --git a/core/api/src/main/java/org/onosproject/net/group/GroupOperation.java b/core/api/src/main/java/org/onosproject/net/group/GroupOperation.java index 44d7e88df9..5a66aca806 100644 --- a/core/api/src/main/java/org/onosproject/net/group/GroupOperation.java +++ b/core/api/src/main/java/org/onosproject/net/group/GroupOperation.java @@ -17,6 +17,8 @@ package org.onosproject.net.group; import static com.google.common.base.Preconditions.checkNotNull; +import java.util.Objects; + import org.onosproject.core.GroupId; /** @@ -142,4 +144,37 @@ public final class GroupOperation { public GroupBuckets buckets() { return this.buckets; } + + @Override + /* + * The deviceId, type and buckets are used for hash. + * + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + public int hashCode() { + return (buckets != null) ? Objects.hash(groupId, opType, buckets) : + Objects.hash(groupId, opType); + } + + @Override + /* + * The deviceId, type and buckets should be same. + * + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof GroupOperation) { + GroupOperation that = (GroupOperation) obj; + return Objects.equals(groupId, that.groupId) && + Objects.equals(opType, that.opType) && + Objects.equals(buckets, that.buckets); + + } + return false; + } } \ No newline at end of file diff --git a/core/api/src/main/java/org/onosproject/net/group/GroupProviderRegistry.java b/core/api/src/main/java/org/onosproject/net/group/GroupProviderRegistry.java new file mode 100644 index 0000000000..d45789db2d --- /dev/null +++ b/core/api/src/main/java/org/onosproject/net/group/GroupProviderRegistry.java @@ -0,0 +1,25 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * 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.net.group; + +import org.onosproject.net.provider.ProviderRegistry; + +/** + * Abstraction for a group provider registry. + */ +public interface GroupProviderRegistry + extends ProviderRegistry { +} diff --git a/core/api/src/main/java/org/onosproject/net/group/GroupService.java b/core/api/src/main/java/org/onosproject/net/group/GroupService.java index 1fd9984ed0..8502aea203 100644 --- a/core/api/src/main/java/org/onosproject/net/group/GroupService.java +++ b/core/api/src/main/java/org/onosproject/net/group/GroupService.java @@ -16,7 +16,6 @@ package org.onosproject.net.group; import org.onosproject.core.ApplicationId; -import org.onosproject.net.Device; import org.onosproject.net.DeviceId; /** @@ -27,7 +26,7 @@ import org.onosproject.net.DeviceId; * specified in a group. * "group" can also be used for grouping common actions of different flows, * so that in some scenarios only one group entry required to be modified - * for all the referencing flow entries instead of modifying all of them + * for all the referencing flow entries instead of modifying all of them. * * This implements semantics of a distributed authoritative group store * where the master copy of the groups lies with the controller and @@ -60,7 +59,7 @@ public interface GroupService { * NOTE1: The presence of group object in the system does not * guarantee that the "group" is actually created in device. * GROUP_ADDED notification would confirm the creation of - * this group in data plane + * this group in data plane. * * @param deviceId device identifier * @param appCookie application cookie to be used for lookup @@ -73,7 +72,7 @@ public interface GroupService { * Appends buckets to existing group. The caller can optionally * associate a new cookie during this updation. GROUP_UPDATED or * GROUP_UPDATE_FAILED notifications would be provided along with - * cookie depending on the result of the operation on the device + * cookie depending on the result of the operation on the device. * * @param deviceId device identifier * @param oldCookie cookie to be used to retrieve the existing group @@ -91,7 +90,7 @@ public interface GroupService { * Removes buckets from existing group. The caller can optionally * associate a new cookie during this updation. GROUP_UPDATED or * GROUP_UPDATE_FAILED notifications would be provided along with - * cookie depending on the result of the operation on the device + * cookie depending on the result of the operation on the device. * * @param deviceId device identifier * @param oldCookie cookie to be used to retrieve the existing group @@ -99,7 +98,7 @@ public interface GroupService { * @param newCookie immutable cookie to be used post update operation * @param appId Application Id */ - void removeBucketsFromGroup(Device deviceId, + void removeBucketsFromGroup(DeviceId deviceId, GroupKey oldCookie, GroupBuckets buckets, GroupKey newCookie, @@ -109,13 +108,13 @@ public interface GroupService { * Deletes a group associated to an application cookie. * GROUP_DELETED or GROUP_DELETE_FAILED notifications would be * provided along with cookie depending on the result of the - * operation on the device + * operation on the device. * * @param deviceId device identifier * @param appCookie application cookie to be used for lookup * @param appId Application Id */ - void removeGroup(Device deviceId, GroupKey appCookie, ApplicationId appId); + void removeGroup(DeviceId deviceId, GroupKey appCookie, ApplicationId appId); /** * Retrieves all groups created by an application in the specified device @@ -125,7 +124,7 @@ public interface GroupService { * @param appId application id * @return collection of immutable group objects created by the application */ - Iterable getGroups(Device deviceId, ApplicationId appId); + Iterable getGroups(DeviceId deviceId, ApplicationId appId); /** * Adds the specified group listener. diff --git a/core/api/src/main/java/org/onosproject/net/group/GroupStore.java b/core/api/src/main/java/org/onosproject/net/group/GroupStore.java index 22914f9b37..2fc7030a90 100644 --- a/core/api/src/main/java/org/onosproject/net/group/GroupStore.java +++ b/core/api/src/main/java/org/onosproject/net/group/GroupStore.java @@ -73,12 +73,14 @@ public interface GroupStore extends Store { * @param deviceId the device ID * @param oldAppCookie the current group key * @param type update type - * @param newGroupDesc group description with updates + * @param newBuckets group buckets for updates + * @param newAppCookie optional new group key */ void updateGroupDescription(DeviceId deviceId, GroupKey oldAppCookie, UpdateType type, - GroupDescription newGroupDesc); + GroupBuckets newBuckets, + GroupKey newAppCookie); /** * Triggers deleting the existing group entry. @@ -102,4 +104,43 @@ public interface GroupStore extends Store { * @param group group entry */ void removeGroupEntry(Group group); + + /** + * A group entry that is present in switch but not in the store. + * + * @param group group entry + */ + void addOrUpdateExtraneousGroupEntry(Group group); + + /** + * Remove the group entry from extraneous database. + * + * @param group group entry + */ + void removeExtraneousGroupEntry(Group group); + + /** + * Returns the extraneous groups associated with a device. + * + * @param deviceId the device ID + * + * @return the extraneous group entries + */ + Iterable getExtraneousGroups(DeviceId deviceId); + + /** + * Indicates the first group audit is completed. + * + * @param deviceId the device ID + */ + void deviceInitialAuditCompleted(DeviceId deviceId); + + /** + * Retrieves the initial group audit status for a device. + * + * @param deviceId the device ID + * + * @return initial group audit status + */ + boolean deviceInitialAuditStatus(DeviceId deviceId); } diff --git a/core/api/src/main/java/org/onosproject/net/group/StoredGroupEntry.java b/core/api/src/main/java/org/onosproject/net/group/StoredGroupEntry.java index b3557b48dd..297663f1db 100644 --- a/core/api/src/main/java/org/onosproject/net/group/StoredGroupEntry.java +++ b/core/api/src/main/java/org/onosproject/net/group/StoredGroupEntry.java @@ -49,4 +49,10 @@ public interface StoredGroupEntry extends Group { */ void setBytes(long bytes); + /** + * Sets number of flow rules or groups referencing this group entry. + * + * @param referenceCount reference count + */ + void setReferenceCount(long referenceCount); } diff --git a/core/net/src/main/java/org/onosproject/net/group/impl/GroupManager.java b/core/net/src/main/java/org/onosproject/net/group/impl/GroupManager.java new file mode 100644 index 0000000000..f54f85eb4b --- /dev/null +++ b/core/net/src/main/java/org/onosproject/net/group/impl/GroupManager.java @@ -0,0 +1,366 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * 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.net.group.impl; + +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.core.ApplicationId; +import org.onosproject.event.AbstractListenerRegistry; +import org.onosproject.event.EventDeliveryService; +import org.onosproject.net.DeviceId; +import org.onosproject.net.group.Group; +import org.onosproject.net.group.GroupBuckets; +import org.onosproject.net.group.GroupDescription; +import org.onosproject.net.group.GroupEvent; +import org.onosproject.net.group.GroupKey; +import org.onosproject.net.group.GroupListener; +import org.onosproject.net.group.GroupOperation; +import org.onosproject.net.group.GroupOperations; +import org.onosproject.net.group.GroupProvider; +import org.onosproject.net.group.GroupProviderRegistry; +import org.onosproject.net.group.GroupProviderService; +import org.onosproject.net.group.GroupService; +import org.onosproject.net.group.GroupStore; +import org.onosproject.net.group.GroupStore.UpdateType; +import org.onosproject.net.group.GroupStoreDelegate; +import org.onosproject.net.provider.AbstractProviderRegistry; +import org.onosproject.net.provider.AbstractProviderService; +import org.slf4j.Logger; + +import com.google.common.collect.Sets; + +/** + * Provides implementation of the group service APIs. + */ +@Component(immediate = true) +@Service +public class GroupManager + extends AbstractProviderRegistry + implements GroupService, GroupProviderRegistry { + + private final Logger log = getLogger(getClass()); + + private final AbstractListenerRegistry + listenerRegistry = new AbstractListenerRegistry<>(); + private final GroupStoreDelegate delegate = new InternalGroupStoreDelegate(); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected GroupStore store; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected EventDeliveryService eventDispatcher; + + @Activate + public void activate() { + store.setDelegate(delegate); + eventDispatcher.addSink(GroupEvent.class, listenerRegistry); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + store.unsetDelegate(delegate); + eventDispatcher.removeSink(GroupEvent.class); + log.info("Stopped"); + } + + /** + * Create a group in the specified device with the provided parameters. + * + * @param groupDesc group creation parameters + * + */ + @Override + public void addGroup(GroupDescription groupDesc) { + store.storeGroupDescription(groupDesc); + } + + /** + * Return a group object associated to an application cookie. + * + * NOTE1: The presence of group object in the system does not + * guarantee that the "group" is actually created in device. + * GROUP_ADDED notification would confirm the creation of + * this group in data plane. + * + * @param deviceId device identifier + * @param appCookie application cookie to be used for lookup + * @return group associated with the application cookie or + * NULL if Group is not found for the provided cookie + */ + @Override + public Group getGroup(DeviceId deviceId, GroupKey appCookie) { + return store.getGroup(deviceId, appCookie); + } + + /** + * Append buckets to existing group. The caller can optionally + * associate a new cookie during this updation. GROUP_UPDATED or + * GROUP_UPDATE_FAILED notifications would be provided along with + * cookie depending on the result of the operation on the device. + * + * @param deviceId device identifier + * @param oldCookie cookie to be used to retrieve the existing group + * @param buckets immutable list of group bucket to be added + * @param newCookie immutable cookie to be used post update operation + * @param appId Application Id + */ + @Override + public void addBucketsToGroup(DeviceId deviceId, + GroupKey oldCookie, + GroupBuckets buckets, + GroupKey newCookie, + ApplicationId appId) { + store.updateGroupDescription(deviceId, + oldCookie, + UpdateType.ADD, + buckets, + newCookie); + } + + /** + * Remove buckets from existing group. The caller can optionally + * associate a new cookie during this updation. GROUP_UPDATED or + * GROUP_UPDATE_FAILED notifications would be provided along with + * cookie depending on the result of the operation on the device. + * + * @param deviceId device identifier + * @param oldCookie cookie to be used to retrieve the existing group + * @param buckets immutable list of group bucket to be removed + * @param newCookie immutable cookie to be used post update operation + * @param appId Application Id + */ + @Override + public void removeBucketsFromGroup(DeviceId deviceId, + GroupKey oldCookie, + GroupBuckets buckets, + GroupKey newCookie, + ApplicationId appId) { + store.updateGroupDescription(deviceId, + oldCookie, + UpdateType.REMOVE, + buckets, + newCookie); + } + + /** + * Delete a group associated to an application cookie. + * GROUP_DELETED or GROUP_DELETE_FAILED notifications would be + * provided along with cookie depending on the result of the + * operation on the device. + * + * @param deviceId device identifier + * @param appCookie application cookie to be used for lookup + * @param appId Application Id + */ + @Override + public void removeGroup(DeviceId deviceId, + GroupKey appCookie, + ApplicationId appId) { + store.deleteGroupDescription(deviceId, appCookie); + } + + /** + * Retrieve all groups created by an application in the specified device + * as seen by current controller instance. + * + * @param deviceId device identifier + * @param appId application id + * @return collection of immutable group objects created by the application + */ + @Override + public Iterable getGroups(DeviceId deviceId, + ApplicationId appId) { + return store.getGroups(deviceId); + } + + /** + * Adds the specified group listener. + * + * @param listener group listener + */ + @Override + public void addListener(GroupListener listener) { + listenerRegistry.addListener(listener); + } + + /** + * Removes the specified group listener. + * + * @param listener group listener + */ + @Override + public void removeListener(GroupListener listener) { + listenerRegistry.removeListener(listener); + } + + @Override + protected GroupProviderService createProviderService(GroupProvider provider) { + return new InternalGroupProviderService(provider); + } + + private class InternalGroupStoreDelegate implements GroupStoreDelegate { + @Override + public void notify(GroupEvent event) { + final Group group = event.subject(); + GroupProvider groupProvider = + getProvider(group.deviceId()); + GroupOperations groupOps = null; + switch (event.type()) { + case GROUP_ADD_REQUESTED: + GroupOperation groupAddOp = GroupOperation. + createAddGroupOperation(group.id(), + group.type(), + group.buckets()); + groupOps = new GroupOperations( + Arrays.asList(groupAddOp)); + groupProvider.performGroupOperation(group.deviceId(), groupOps); + break; + + case GROUP_UPDATE_REQUESTED: + GroupOperation groupModifyOp = GroupOperation. + createModifyGroupOperation(group.id(), + group.type(), + group.buckets()); + groupOps = new GroupOperations( + Arrays.asList(groupModifyOp)); + groupProvider.performGroupOperation(group.deviceId(), groupOps); + break; + + case GROUP_REMOVE_REQUESTED: + GroupOperation groupDeleteOp = GroupOperation. + createDeleteGroupOperation(group.id(), + group.type()); + groupOps = new GroupOperations( + Arrays.asList(groupDeleteOp)); + groupProvider.performGroupOperation(group.deviceId(), groupOps); + break; + + case GROUP_ADDED: + case GROUP_UPDATED: + case GROUP_REMOVED: + eventDispatcher.post(event); + break; + + default: + break; + } + } + } + + private class InternalGroupProviderService + extends AbstractProviderService + implements GroupProviderService { + + protected InternalGroupProviderService(GroupProvider provider) { + super(provider); + } + + @Override + public void groupOperationFailed(GroupOperation operation) { + // TODO Auto-generated method stub + + } + + private void groupMissing(Group group) { + checkValidity(); + GroupProvider gp = getProvider(group.deviceId()); + switch (group.state()) { + case PENDING_DELETE: + store.removeGroupEntry(group); + break; + case ADDED: + case PENDING_ADD: + GroupOperation groupAddOp = GroupOperation. + createAddGroupOperation(group.id(), + group.type(), + group.buckets()); + GroupOperations groupOps = new GroupOperations( + Arrays.asList(groupAddOp)); + gp.performGroupOperation(group.deviceId(), groupOps); + break; + default: + log.debug("Group {} has not been installed.", group); + break; + } + } + + + private void extraneousGroup(Group group) { + log.debug("Group {} is on switch but not in store.", group); + checkValidity(); + store.addOrUpdateExtraneousGroupEntry(group); + } + + private void groupAdded(Group group) { + checkValidity(); + + log.trace("Group {}", group); + store.addOrUpdateGroupEntry(group); + } + + @Override + public void pushGroupMetrics(DeviceId deviceId, + Collection groupEntries) { + boolean deviceInitialAuditStatus = + store.deviceInitialAuditStatus(deviceId); + Set southboundGroupEntries = + Sets.newHashSet(groupEntries); + Set storedGroupEntries = + Sets.newHashSet(store.getGroups(deviceId)); + Set extraneousStoredEntries = + Sets.newHashSet(store.getExtraneousGroups(deviceId)); + + for (Iterator it = southboundGroupEntries.iterator(); it.hasNext();) { + Group group = it.next(); + if (storedGroupEntries.remove(group)) { + // we both have the group, let's update some info then. + groupAdded(group); + it.remove(); + } + } + for (Group group : southboundGroupEntries) { + // there are groups in the switch that aren't in the store + extraneousStoredEntries.remove(group); + extraneousGroup(group); + } + for (Group group : storedGroupEntries) { + // there are groups in the store that aren't in the switch + groupMissing(group); + } + for (Group group : extraneousStoredEntries) { + // there are groups in the extraneous store that + // aren't in the switch + store.removeExtraneousGroupEntry(group); + } + + if (!deviceInitialAuditStatus) { + store.deviceInitialAuditCompleted(deviceId); + } + } + } +} diff --git a/core/net/src/main/java/org/onosproject/net/group/impl/package-info.java b/core/net/src/main/java/org/onosproject/net/group/impl/package-info.java new file mode 100644 index 0000000000..641ab44140 --- /dev/null +++ b/core/net/src/main/java/org/onosproject/net/group/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * 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. + */ + +/** + * Core subsystem for group state. + */ +package org.onosproject.net.group.impl; \ No newline at end of file diff --git a/core/net/src/test/java/org/onosproject/net/group/impl/GroupManagerTest.java b/core/net/src/test/java/org/onosproject/net/group/impl/GroupManagerTest.java new file mode 100644 index 0000000000..2e1bd210f6 --- /dev/null +++ b/core/net/src/test/java/org/onosproject/net/group/impl/GroupManagerTest.java @@ -0,0 +1,400 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * 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.net.group.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.MacAddress; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.DefaultApplicationId; +import org.onosproject.core.DefaultGroupId; +import org.onosproject.core.GroupId; +import org.onosproject.event.impl.TestEventDispatcher; +import org.onosproject.net.DeviceId; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.group.DefaultGroup; +import org.onosproject.net.group.DefaultGroupBucket; +import org.onosproject.net.group.DefaultGroupDescription; +import org.onosproject.net.group.Group; +import org.onosproject.net.group.GroupBucket; +import org.onosproject.net.group.GroupBuckets; +import org.onosproject.net.group.GroupDescription; +import org.onosproject.net.group.GroupEvent; +import org.onosproject.net.group.GroupKey; +import org.onosproject.net.group.GroupListener; +import org.onosproject.net.group.GroupOperation; +import org.onosproject.net.group.GroupOperations; +import org.onosproject.net.group.GroupProvider; +import org.onosproject.net.group.GroupProviderRegistry; +import org.onosproject.net.group.GroupProviderService; +import org.onosproject.net.group.GroupService; +import org.onosproject.net.group.StoredGroupEntry; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.store.trivial.impl.SimpleGroupStore; + +import com.google.common.collect.Iterables; + +/** + * Test codifying the group service & group provider service contracts. + */ +public class GroupManagerTest { + + private static final ProviderId PID = new ProviderId("of", "groupfoo"); + private static final DeviceId DID = DeviceId.deviceId("of:001"); + + private GroupManager mgr; + private GroupService groupService; + private GroupProviderRegistry providerRegistry; + private TestGroupListener internalListener = new TestGroupListener(); + private GroupListener listener = internalListener; + private TestGroupProvider internalProvider; + private GroupProvider provider; + private GroupProviderService providerService; + private ApplicationId appId; + + @Before + public void setUp() { + mgr = new GroupManager(); + groupService = mgr; + mgr.store = new SimpleGroupStore(); + mgr.eventDispatcher = new TestEventDispatcher(); + providerRegistry = mgr; + + mgr.activate(); + mgr.addListener(listener); + + internalProvider = new TestGroupProvider(PID); + provider = internalProvider; + providerService = providerRegistry.register(provider); + appId = new DefaultApplicationId(2, "org.groupmanager.test"); + assertTrue("provider should be registered", + providerRegistry.getProviders().contains(provider.id())); + } + + @After + public void tearDown() { + providerRegistry.unregister(provider); + assertFalse("provider should not be registered", + providerRegistry.getProviders().contains(provider.id())); + mgr.removeListener(listener); + mgr.deactivate(); + mgr.eventDispatcher = null; + } + + private class TestGroupKey implements GroupKey { + private String groupId; + + public TestGroupKey(String id) { + this.groupId = id; + } + + public String id() { + return this.groupId; + } + + @Override + public int hashCode() { + return groupId.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof TestGroupKey) { + return this.groupId.equals(((TestGroupKey) obj).id()); + } + return false; + } + } + + /** + * Tests group service north bound and south bound interfaces. + * The following operations are tested: + * a)Tests group creation before the device group AUDIT completes + * b)Tests initial device group AUDIT process + * c)Tests deletion process of any extraneous groups + * d)Tests execution of any pending group creation requests + * after the device group AUDIT completes + * e)Tests re-apply process of any missing groups + * f)Tests event notifications after receiving confirmation for + * any operations from data plane + * g)Tests group bucket modifications (additions and deletions) + * h)Tests group deletion + */ + @Test + public void testGroupService() { + PortNumber[] ports1 = {PortNumber.portNumber(31), + PortNumber.portNumber(32)}; + PortNumber[] ports2 = {PortNumber.portNumber(41), + PortNumber.portNumber(42)}; + // Test Group creation before AUDIT process + TestGroupKey key = new TestGroupKey("group1BeforeAudit"); + List buckets = new ArrayList(); + List outPorts = new ArrayList(); + outPorts.addAll(Arrays.asList(ports1)); + outPorts.addAll(Arrays.asList(ports2)); + for (PortNumber portNumber: outPorts) { + TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder(); + tBuilder.setOutput(portNumber) + .setEthDst(MacAddress.valueOf("00:00:00:00:00:02")) + .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01")) + .pushMpls() + .setMpls(106); + buckets.add(DefaultGroupBucket.createSelectGroupBucket( + tBuilder.build())); + } + GroupBuckets groupBuckets = new GroupBuckets(buckets); + GroupDescription newGroupDesc = new DefaultGroupDescription(DID, + Group.Type.SELECT, + groupBuckets, + key, + appId); + groupService.addGroup(newGroupDesc); + internalProvider.validate(DID, null); + assertEquals(null, groupService.getGroup(DID, key)); + assertEquals(0, Iterables.size(groupService.getGroups(DID, appId))); + + // Test initial group audit process + GroupId gId1 = new DefaultGroupId(1); + Group group1 = createSouthboundGroupEntry(gId1, + Arrays.asList(ports1), + 0); + GroupId gId2 = new DefaultGroupId(2); + // Non zero reference count will make the group manager to queue + // the extraneous groups until reference count is zero. + Group group2 = createSouthboundGroupEntry(gId2, + Arrays.asList(ports2), + 2); + List groupEntries = Arrays.asList(group1, group2); + providerService.pushGroupMetrics(DID, groupEntries); + // First group metrics would trigger the device audit completion + // post which all pending group requests are also executed. + Group createdGroup = groupService.getGroup(DID, key); + int createdGroupId = createdGroup.id().id(); + assertNotEquals(gId1.id(), createdGroupId); + assertNotEquals(gId2.id(), createdGroupId); + List expectedGroupOps = Arrays.asList( + GroupOperation.createDeleteGroupOperation(gId1, + Group.Type.SELECT), + GroupOperation.createAddGroupOperation( + createdGroup.id(), + Group.Type.SELECT, + groupBuckets)); + internalProvider.validate(DID, expectedGroupOps); + + group1 = createSouthboundGroupEntry(gId1, + Arrays.asList(ports1), + 0); + group2 = createSouthboundGroupEntry(gId2, + Arrays.asList(ports2), + 0); + groupEntries = Arrays.asList(group1, group2); + providerService.pushGroupMetrics(DID, groupEntries); + expectedGroupOps = Arrays.asList( + GroupOperation.createDeleteGroupOperation(gId1, + Group.Type.SELECT), + GroupOperation.createDeleteGroupOperation(gId2, + Group.Type.SELECT), + GroupOperation.createAddGroupOperation(createdGroup.id(), + Group.Type.SELECT, + groupBuckets)); + internalProvider.validate(DID, expectedGroupOps); + + createdGroup = new DefaultGroup(createdGroup.id(), + DID, + Group.Type.SELECT, + groupBuckets); + groupEntries = Arrays.asList(createdGroup); + providerService.pushGroupMetrics(DID, groupEntries); + internalListener.validateEvent(Arrays.asList(GroupEvent.Type.GROUP_ADDED)); + + // Test group add bucket operations + TestGroupKey addKey = new TestGroupKey("group1AddBuckets"); + PortNumber[] addPorts = {PortNumber.portNumber(51), + PortNumber.portNumber(52)}; + outPorts.clear(); + outPorts.addAll(Arrays.asList(addPorts)); + List addBuckets = new ArrayList(); + for (PortNumber portNumber: outPorts) { + TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder(); + tBuilder.setOutput(portNumber) + .setEthDst(MacAddress.valueOf("00:00:00:00:00:02")) + .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01")) + .pushMpls() + .setMpls(106); + addBuckets.add(DefaultGroupBucket.createSelectGroupBucket( + tBuilder.build())); + buckets.add(DefaultGroupBucket.createSelectGroupBucket( + tBuilder.build())); + } + GroupBuckets groupAddBuckets = new GroupBuckets(addBuckets); + groupService.addBucketsToGroup(DID, + key, + groupAddBuckets, + addKey, + appId); + GroupBuckets updatedBuckets = new GroupBuckets(buckets); + expectedGroupOps = Arrays.asList( + GroupOperation.createModifyGroupOperation(createdGroup.id(), + Group.Type.SELECT, + updatedBuckets)); + internalProvider.validate(DID, expectedGroupOps); + Group existingGroup = groupService.getGroup(DID, addKey); + groupEntries = Arrays.asList(existingGroup); + providerService.pushGroupMetrics(DID, groupEntries); + internalListener.validateEvent(Arrays.asList(GroupEvent.Type.GROUP_UPDATED)); + + // Test group remove bucket operations + TestGroupKey removeKey = new TestGroupKey("group1RemoveBuckets"); + PortNumber[] removePorts = {PortNumber.portNumber(31), + PortNumber.portNumber(32)}; + outPorts.clear(); + outPorts.addAll(Arrays.asList(removePorts)); + List removeBuckets = new ArrayList(); + for (PortNumber portNumber: outPorts) { + TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder(); + tBuilder.setOutput(portNumber) + .setEthDst(MacAddress.valueOf("00:00:00:00:00:02")) + .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01")) + .pushMpls() + .setMpls(106); + removeBuckets.add(DefaultGroupBucket.createSelectGroupBucket( + tBuilder.build())); + buckets.remove(DefaultGroupBucket.createSelectGroupBucket( + tBuilder.build())); + } + GroupBuckets groupRemoveBuckets = new GroupBuckets(removeBuckets); + groupService.removeBucketsFromGroup(DID, + addKey, + groupRemoveBuckets, + removeKey, + appId); + updatedBuckets = new GroupBuckets(buckets); + expectedGroupOps = Arrays.asList( + GroupOperation.createModifyGroupOperation(createdGroup.id(), + Group.Type.SELECT, + updatedBuckets)); + internalProvider.validate(DID, expectedGroupOps); + existingGroup = groupService.getGroup(DID, removeKey); + groupEntries = Arrays.asList(existingGroup); + providerService.pushGroupMetrics(DID, groupEntries); + internalListener.validateEvent(Arrays.asList(GroupEvent.Type.GROUP_UPDATED)); + + // Test group remove operations + groupService.removeGroup(DID, removeKey, appId); + expectedGroupOps = Arrays.asList( + GroupOperation.createDeleteGroupOperation(createdGroup.id(), + Group.Type.SELECT)); + internalProvider.validate(DID, expectedGroupOps); + groupEntries = Collections.emptyList(); + providerService.pushGroupMetrics(DID, groupEntries); + internalListener.validateEvent(Arrays.asList(GroupEvent.Type.GROUP_REMOVED)); + } + + private Group createSouthboundGroupEntry(GroupId gId, + List ports, + long referenceCount) { + List outPorts = new ArrayList(); + outPorts.addAll(ports); + + List buckets = new ArrayList(); + for (PortNumber portNumber: outPorts) { + TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder(); + tBuilder.setOutput(portNumber) + .setEthDst(MacAddress.valueOf("00:00:00:00:00:02")) + .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01")) + .pushMpls() + .setMpls(106); + buckets.add(DefaultGroupBucket.createSelectGroupBucket( + tBuilder.build())); + } + GroupBuckets groupBuckets = new GroupBuckets(buckets); + StoredGroupEntry group = new DefaultGroup( + gId, DID, Group.Type.SELECT, groupBuckets); + group.setReferenceCount(referenceCount); + return group; + } + + private static class TestGroupListener implements GroupListener { + final List events = new ArrayList<>(); + + @Override + public void event(GroupEvent event) { + events.add(event); + } + + public void validateEvent(List expectedEvents) { + int i = 0; + System.err.println("events :" + events); + for (GroupEvent e : events) { + assertEquals("unexpected event", expectedEvents.get(i), e.type()); + i++; + } + assertEquals("mispredicted number of events", + expectedEvents.size(), events.size()); + events.clear(); + } + } + + private class TestGroupProvider + extends AbstractProvider implements GroupProvider { + DeviceId lastDeviceId; + List groupOperations = new ArrayList(); + + protected TestGroupProvider(ProviderId id) { + super(id); + } + + @Override + public void performGroupOperation(DeviceId deviceId, + GroupOperations groupOps) { + lastDeviceId = deviceId; + groupOperations.addAll(groupOps.operations()); + } + + public void validate(DeviceId expectedDeviceId, + List expectedGroupOps) { + if (expectedGroupOps == null) { + assertTrue("events generated", groupOperations.isEmpty()); + return; + } + + assertEquals(lastDeviceId, expectedDeviceId); + assertTrue((this.groupOperations.containsAll(expectedGroupOps) && + expectedGroupOps.containsAll(groupOperations))); + + groupOperations.clear(); + lastDeviceId = null; + } + + } + +} + + diff --git a/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleGroupStore.java b/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleGroupStore.java index c82ebc2bb4..8c7de08c60 100644 --- a/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleGroupStore.java +++ b/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleGroupStore.java @@ -19,6 +19,7 @@ import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsent import static org.slf4j.LoggerFactory.getLogger; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -62,11 +63,21 @@ public class SimpleGroupStore private final Logger log = getLogger(getClass()); + private final int dummyId = 0xffffffff; + private final GroupId dummyGroupId = new DefaultGroupId(dummyId); + // inner Map is per device group table private final ConcurrentMap> groupEntriesByKey = new ConcurrentHashMap<>(); private final ConcurrentMap> groupEntriesById = new ConcurrentHashMap<>(); + private final ConcurrentMap> + pendingGroupEntriesByKey = new ConcurrentHashMap<>(); + private final ConcurrentMap> + extraneousGroupEntriesById = new ConcurrentHashMap<>(); + + private final HashMap deviceAuditStatus = + new HashMap(); private final AtomicInteger groupIdGen = new AtomicInteger(); @@ -82,14 +93,26 @@ public class SimpleGroupStore log.info("Stopped"); } - private static NewConcurrentHashMap lazyEmptyGroupKeyTable() { + private static NewConcurrentHashMap + lazyEmptyGroupKeyTable() { return NewConcurrentHashMap.ifNeeded(); } - private static NewConcurrentHashMap lazyEmptyGroupIdTable() { + private static NewConcurrentHashMap + lazyEmptyGroupIdTable() { return NewConcurrentHashMap.ifNeeded(); } + private static NewConcurrentHashMap + lazyEmptyPendingGroupKeyTable() { + return NewConcurrentHashMap.ifNeeded(); + } + + private static NewConcurrentHashMap + lazyEmptyExtraneousGroupIdTable() { + return NewConcurrentHashMap.ifNeeded(); + } + /** * Returns the group key table for specified device. * @@ -112,6 +135,31 @@ public class SimpleGroupStore deviceId, lazyEmptyGroupIdTable()); } + /** + * Returns the pending group key table for specified device. + * + * @param deviceId identifier of the device + * @return Map representing group key table of given device. + */ + private ConcurrentMap + getPendingGroupKeyTable(DeviceId deviceId) { + return createIfAbsentUnchecked(pendingGroupEntriesByKey, + deviceId, lazyEmptyPendingGroupKeyTable()); + } + + /** + * Returns the extraneous group id table for specified device. + * + * @param deviceId identifier of the device + * @return Map representing group key table of given device. + */ + private ConcurrentMap + getExtraneousGroupIdTable(DeviceId deviceId) { + return createIfAbsentUnchecked(extraneousGroupEntriesById, + deviceId, + lazyEmptyExtraneousGroupIdTable()); + } + /** * Returns the number of groups for the specified device in the store. * @@ -133,20 +181,16 @@ public class SimpleGroupStore @Override public Iterable getGroups(DeviceId deviceId) { // flatten and make iterator unmodifiable - if (groupEntriesByKey.get(deviceId) != null) { - return FluentIterable.from(groupEntriesByKey.get(deviceId).values()) - .transform( - new Function() { + return FluentIterable.from(getGroupKeyTable(deviceId).values()) + .transform( + new Function() { - @Override - public Group apply( - StoredGroupEntry input) { - return input; - } - }); - } else { - return null; - } + @Override + public Group apply( + StoredGroupEntry input) { + return input; + } + }); } /** @@ -164,6 +208,30 @@ public class SimpleGroupStore null; } + private int getFreeGroupIdValue(DeviceId deviceId) { + int freeId = groupIdGen.incrementAndGet(); + + while (true) { + Group existing = ( + groupEntriesById.get(deviceId) != null) ? + groupEntriesById.get(deviceId).get(new DefaultGroupId(freeId)) : + null; + if (existing == null) { + existing = ( + extraneousGroupEntriesById.get(deviceId) != null) ? + extraneousGroupEntriesById.get(deviceId). + get(new DefaultGroupId(freeId)) : + null; + } + if (existing != null) { + freeId = groupIdGen.incrementAndGet(); + } else { + break; + } + } + return freeId; + } + /** * Stores a new group entry using the information from group description. * @@ -171,16 +239,32 @@ public class SimpleGroupStore */ @Override public void storeGroupDescription(GroupDescription groupDesc) { - /* Check if a group is existing with the same key */ + // Check if a group is existing with the same key if (getGroup(groupDesc.deviceId(), groupDesc.appCookie()) != null) { return; } - /* Get a new group identifier */ - GroupId id = new DefaultGroupId(groupIdGen.incrementAndGet()); - /* Create a group entry object */ + if (deviceAuditStatus.get(groupDesc.deviceId()) == null) { + // Device group audit has not completed yet + // Add this group description to pending group key table + // Create a group entry object with Dummy Group ID + StoredGroupEntry group = new DefaultGroup(dummyGroupId, groupDesc); + group.setState(GroupState.WAITING_AUDIT_COMPLETE); + ConcurrentMap pendingKeyTable = + getPendingGroupKeyTable(groupDesc.deviceId()); + pendingKeyTable.put(groupDesc.appCookie(), group); + return; + } + + storeGroupDescriptionInternal(groupDesc); + } + + private void storeGroupDescriptionInternal(GroupDescription groupDesc) { + // Get a new group identifier + GroupId id = new DefaultGroupId(getFreeGroupIdValue(groupDesc.deviceId())); + // Create a group entry object StoredGroupEntry group = new DefaultGroup(id, groupDesc); - /* Insert the newly created group entry into concurrent key and id maps */ + // Insert the newly created group entry into concurrent key and id maps ConcurrentMap keyTable = getGroupKeyTable(groupDesc.deviceId()); keyTable.put(groupDesc.appCookie(), group); @@ -198,14 +282,16 @@ public class SimpleGroupStore * @param deviceId the device ID * @param oldAppCookie the current group key * @param type update type - * @param newGroupDesc group description with updates + * @param newBuckets group buckets for updates + * @param newAppCookie optional new group key */ @Override public void updateGroupDescription(DeviceId deviceId, GroupKey oldAppCookie, UpdateType type, - GroupDescription newGroupDesc) { - /* Check if a group is existing with the provided key */ + GroupBuckets newBuckets, + GroupKey newAppCookie) { + // Check if a group is existing with the provided key Group oldGroup = getGroup(deviceId, oldAppCookie); if (oldGroup == null) { return; @@ -213,15 +299,16 @@ public class SimpleGroupStore List newBucketList = getUpdatedBucketList(oldGroup, type, - newGroupDesc.buckets()); + newBuckets); if (newBucketList != null) { - /* Create a new group object from the old group */ + // Create a new group object from the old group GroupBuckets updatedBuckets = new GroupBuckets(newBucketList); + GroupKey newCookie = (newAppCookie != null) ? newAppCookie : oldAppCookie; GroupDescription updatedGroupDesc = new DefaultGroupDescription( oldGroup.deviceId(), oldGroup.type(), updatedBuckets, - newGroupDesc.appCookie(), + newCookie, oldGroup.appId()); StoredGroupEntry newGroup = new DefaultGroup(oldGroup.id(), updatedGroupDesc); @@ -229,9 +316,7 @@ public class SimpleGroupStore newGroup.setLife(oldGroup.life()); newGroup.setPackets(oldGroup.packets()); newGroup.setBytes(oldGroup.bytes()); - /* Remove the old entry from maps and add new entry - * using new key - */ + // Remove the old entry from maps and add new entry using new key ConcurrentMap keyTable = getGroupKeyTable(oldGroup.deviceId()); ConcurrentMap idTable = @@ -253,9 +338,8 @@ public class SimpleGroupStore boolean groupDescUpdated = false; if (type == UpdateType.ADD) { - /* Check if the any of the new buckets are part of the - * old bucket list - */ + // Check if the any of the new buckets are part of + // the old bucket list for (GroupBucket addBucket:buckets.buckets()) { if (!newBucketList.contains(addBucket)) { newBucketList.add(addBucket); @@ -263,9 +347,8 @@ public class SimpleGroupStore } } } else if (type == UpdateType.REMOVE) { - /* Check if the to be removed buckets are part of the - * old bucket list - */ + // Check if the to be removed buckets are part of the + // old bucket list for (GroupBucket removeBucket:buckets.buckets()) { if (newBucketList.contains(removeBucket)) { newBucketList.remove(removeBucket); @@ -290,7 +373,7 @@ public class SimpleGroupStore @Override public void deleteGroupDescription(DeviceId deviceId, GroupKey appCookie) { - /* Check if a group is existing with the provided key */ + // Check if a group is existing with the provided key StoredGroupEntry existing = (groupEntriesByKey.get(deviceId) != null) ? groupEntriesByKey.get(deviceId).get(appCookie) : null; @@ -362,4 +445,56 @@ public class SimpleGroupStore notifyDelegate(new GroupEvent(Type.GROUP_REMOVED, existing)); } } + + @Override + public void deviceInitialAuditCompleted(DeviceId deviceId) { + synchronized (deviceAuditStatus) { + deviceAuditStatus.putIfAbsent(deviceId, true); + // Execute all pending group requests + ConcurrentMap pendingGroupRequests = + getPendingGroupKeyTable(deviceId); + for (Group group:pendingGroupRequests.values()) { + GroupDescription tmp = new DefaultGroupDescription( + group.deviceId(), + group.type(), + group.buckets(), + group.appCookie(), + group.appId()); + storeGroupDescriptionInternal(tmp); + } + getPendingGroupKeyTable(deviceId).clear(); + } + } + + @Override + public boolean deviceInitialAuditStatus(DeviceId deviceId) { + synchronized (deviceAuditStatus) { + return (deviceAuditStatus.get(deviceId) != null) ? true : false; + } + } + + @Override + public void addOrUpdateExtraneousGroupEntry(Group group) { + ConcurrentMap extraneousIdTable = + getExtraneousGroupIdTable(group.deviceId()); + extraneousIdTable.put(group.id(), group); + // Check the reference counter + if (group.referenceCount() == 0) { + notifyDelegate(new GroupEvent(Type.GROUP_REMOVE_REQUESTED, group)); + } + } + + @Override + public void removeExtraneousGroupEntry(Group group) { + ConcurrentMap extraneousIdTable = + getExtraneousGroupIdTable(group.deviceId()); + extraneousIdTable.remove(group.id()); + } + + @Override + public Iterable getExtraneousGroups(DeviceId deviceId) { + // flatten and make iterator unmodifiable + return FluentIterable.from( + getExtraneousGroupIdTable(deviceId).values()); + } } diff --git a/core/store/trivial/src/test/java/org/onosproject/store/trivial/impl/SimpleGroupStoreTest.java b/core/store/trivial/src/test/java/org/onosproject/store/trivial/impl/SimpleGroupStoreTest.java index b1f03f4efa..277e1ca818 100644 --- a/core/store/trivial/src/test/java/org/onosproject/store/trivial/impl/SimpleGroupStoreTest.java +++ b/core/store/trivial/src/test/java/org/onosproject/store/trivial/impl/SimpleGroupStoreTest.java @@ -40,8 +40,10 @@ import org.onosproject.net.group.GroupBuckets; import org.onosproject.net.group.GroupDescription; import org.onosproject.net.group.GroupEvent; import org.onosproject.net.group.GroupKey; -import org.onosproject.net.group.GroupStoreDelegate; import org.onosproject.net.group.GroupStore.UpdateType; +import org.onosproject.net.group.GroupStoreDelegate; + +import com.google.common.collect.Iterables; /** * Test of the simple DeviceStore implementation. @@ -135,8 +137,24 @@ public class SimpleGroupStoreTest { } } + /** + * Tests group store operations. The following operations are tested: + * a)Tests device group audit completion status change + * b)Tests storeGroup operation + * c)Tests getGroupCount operation + * d)Tests getGroup operation + * e)Tests getGroups operation + * f)Tests addOrUpdateGroupEntry operation from southbound + * g)Tests updateGroupDescription for ADD operation from northbound + * h)Tests updateGroupDescription for REMOVE operation from northbound + * i)Tests deleteGroupDescription operation from northbound + * j)Tests removeGroupEntry operation from southbound + */ @Test public void testGroupStoreOperations() { + // Set the Device AUDIT completed in the store + simpleGroupStore.deviceInitialAuditCompleted(D1); + ApplicationId appId = new DefaultApplicationId(2, "org.groupstore.test"); TestGroupKey key = new TestGroupKey("group1"); @@ -169,17 +187,17 @@ public class SimpleGroupStoreTest { groupBuckets, GroupEvent.Type.GROUP_ADD_REQUESTED); simpleGroupStore.setDelegate(checkStoreGroupDelegate); - /* Testing storeGroup operation */ + // Testing storeGroup operation simpleGroupStore.storeGroupDescription(groupDesc); - /* Testing getGroupCount operation */ + // Testing getGroupCount operation assertEquals(1, simpleGroupStore.getGroupCount(D1)); - /* Testing getGroup operation */ + // Testing getGroup operation Group createdGroup = simpleGroupStore.getGroup(D1, key); checkStoreGroupDelegate.verifyGroupId(createdGroup.id()); - /* Testing getGroups operation */ + // Testing getGroups operation Iterable createdGroups = simpleGroupStore.getGroups(D1); int groupCount = 0; for (Group group:createdGroups) { @@ -189,7 +207,7 @@ public class SimpleGroupStoreTest { assertEquals(1, groupCount); simpleGroupStore.unsetDelegate(checkStoreGroupDelegate); - /* Testing addOrUpdateGroupEntry operation from southbound */ + // Testing addOrUpdateGroupEntry operation from southbound InternalGroupStoreDelegate addGroupEntryDelegate = new InternalGroupStoreDelegate(key, groupBuckets, @@ -198,7 +216,7 @@ public class SimpleGroupStoreTest { simpleGroupStore.addOrUpdateGroupEntry(createdGroup); simpleGroupStore.unsetDelegate(addGroupEntryDelegate); - /* Testing updateGroupDescription for ADD operation from northbound */ + // Testing updateGroupDescription for ADD operation from northbound TestGroupKey addKey = new TestGroupKey("group1AddBuckets"); PortNumber[] newNeighborPorts = {PortNumber.portNumber(41), PortNumber.portNumber(42)}; @@ -225,19 +243,14 @@ public class SimpleGroupStoreTest { updatedGroupBuckets, GroupEvent.Type.GROUP_UPDATE_REQUESTED); simpleGroupStore.setDelegate(updateGroupDescDelegate); - GroupDescription newGroupDesc = new DefaultGroupDescription( - D1, - Group.Type.SELECT, - toAddGroupBuckets, - addKey, - appId); simpleGroupStore.updateGroupDescription(D1, key, UpdateType.ADD, - newGroupDesc); + toAddGroupBuckets, + addKey); simpleGroupStore.unsetDelegate(updateGroupDescDelegate); - /* Testing updateGroupDescription for REMOVE operation from northbound */ + // Testing updateGroupDescription for REMOVE operation from northbound TestGroupKey removeKey = new TestGroupKey("group1RemoveBuckets"); List toRemoveBuckets = new ArrayList(); toRemoveBuckets.add(updatedGroupBuckets.buckets().get(0)); @@ -252,23 +265,18 @@ public class SimpleGroupStoreTest { remainingGroupBuckets, GroupEvent.Type.GROUP_UPDATE_REQUESTED); simpleGroupStore.setDelegate(removeGroupDescDelegate); - GroupDescription removeGroupDesc = new DefaultGroupDescription( - D1, - Group.Type.SELECT, - toRemoveGroupBuckets, - removeKey, - appId); simpleGroupStore.updateGroupDescription(D1, addKey, UpdateType.REMOVE, - removeGroupDesc); + toRemoveGroupBuckets, + removeKey); simpleGroupStore.unsetDelegate(removeGroupDescDelegate); - /* Testing getGroup operation */ + // Testing getGroup operation Group existingGroup = simpleGroupStore.getGroup(D1, removeKey); checkStoreGroupDelegate.verifyGroupId(existingGroup.id()); - /* Testing addOrUpdateGroupEntry operation from southbound */ + // Testing addOrUpdateGroupEntry operation from southbound InternalGroupStoreDelegate updateGroupEntryDelegate = new InternalGroupStoreDelegate(removeKey, remainingGroupBuckets, @@ -277,7 +285,7 @@ public class SimpleGroupStoreTest { simpleGroupStore.addOrUpdateGroupEntry(existingGroup); simpleGroupStore.unsetDelegate(updateGroupEntryDelegate); - /* Testing deleteGroupDescription operation from northbound */ + // Testing deleteGroupDescription operation from northbound InternalGroupStoreDelegate deleteGroupDescDelegate = new InternalGroupStoreDelegate(removeKey, remainingGroupBuckets, @@ -286,7 +294,7 @@ public class SimpleGroupStoreTest { simpleGroupStore.deleteGroupDescription(D1, removeKey); simpleGroupStore.unsetDelegate(deleteGroupDescDelegate); - /* Testing removeGroupEntry operation from southbound */ + // Testing removeGroupEntry operation from southbound InternalGroupStoreDelegate removeGroupEntryDelegate = new InternalGroupStoreDelegate(removeKey, remainingGroupBuckets, @@ -294,17 +302,10 @@ public class SimpleGroupStoreTest { simpleGroupStore.setDelegate(removeGroupEntryDelegate); simpleGroupStore.removeGroupEntry(existingGroup); - /* Testing getGroup operation */ + // Testing getGroup operation existingGroup = simpleGroupStore.getGroup(D1, removeKey); assertEquals(null, existingGroup); - Iterable existingGroups = simpleGroupStore.getGroups(D1); - groupCount = 0; - for (Group tmp:existingGroups) { - /* To avoid warning */ - assertEquals(null, tmp); - groupCount++; - } - assertEquals(0, groupCount); + assertEquals(0, Iterables.size(simpleGroupStore.getGroups(D1))); assertEquals(0, simpleGroupStore.getGroupCount(D1)); simpleGroupStore.unsetDelegate(removeGroupEntryDelegate);