ONOS-895: Group manager implementation
Change-Id: Ie183f722fa39012f8de056961715c325e2388e63
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 11ca73c..58ae9a9 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 @@
@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 0e4cac1..eba2c6c 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 @@
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 @@
private long life;
private long packets;
private long bytes;
+ private long referenceCount;
private GroupId id;
/**
@@ -47,6 +51,29 @@
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 @@
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 3fab387..931cc71 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 @@
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 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 25af506..8d374c1 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 @@
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 @@
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 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 @@
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 f7fa507..b7872de 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
@@ -26,6 +26,10 @@
*/
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 @@
* @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 10f4eca..5ca8f30 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 @@
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 44d7e88..5a66aca 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 @@
import static com.google.common.base.Preconditions.checkNotNull;
+import java.util.Objects;
+
import org.onosproject.core.GroupId;
/**
@@ -142,4 +144,37 @@
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 0000000..d45789d
--- /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<GroupProvider, GroupProviderService> {
+}
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 1fd9984..8502aea 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* @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 @@
* 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 @@
* @param appId application id
* @return collection of immutable group objects created by the application
*/
- Iterable<Group> getGroups(Device deviceId, ApplicationId appId);
+ Iterable<Group> 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 22914f9..2fc7030 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 @@
* @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 @@
* @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<Group> 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 b3557b4..297663f 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 @@
*/
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 0000000..f54f85e
--- /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<GroupProvider, GroupProviderService>
+ implements GroupService, GroupProviderRegistry {
+
+ private final Logger log = getLogger(getClass());
+
+ private final AbstractListenerRegistry<GroupEvent, GroupListener>
+ 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<Group> 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<GroupProvider>
+ 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<Group> groupEntries) {
+ boolean deviceInitialAuditStatus =
+ store.deviceInitialAuditStatus(deviceId);
+ Set<Group> southboundGroupEntries =
+ Sets.newHashSet(groupEntries);
+ Set<Group> storedGroupEntries =
+ Sets.newHashSet(store.getGroups(deviceId));
+ Set<Group> extraneousStoredEntries =
+ Sets.newHashSet(store.getExtraneousGroups(deviceId));
+
+ for (Iterator<Group> 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 0000000..641ab44
--- /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 0000000..2e1bd21
--- /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<GroupBucket> buckets = new ArrayList<GroupBucket>();
+ List<PortNumber> outPorts = new ArrayList<PortNumber>();
+ 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<Group> 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<GroupOperation> 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<GroupBucket> addBuckets = new ArrayList<GroupBucket>();
+ 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<GroupBucket> removeBuckets = new ArrayList<GroupBucket>();
+ 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<PortNumber> ports,
+ long referenceCount) {
+ List<PortNumber> outPorts = new ArrayList<PortNumber>();
+ outPorts.addAll(ports);
+
+ List<GroupBucket> buckets = new ArrayList<GroupBucket>();
+ 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<GroupEvent> events = new ArrayList<>();
+
+ @Override
+ public void event(GroupEvent event) {
+ events.add(event);
+ }
+
+ public void validateEvent(List<GroupEvent.Type> 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<GroupOperation> groupOperations = new ArrayList<GroupOperation>();
+
+ 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<GroupOperation> 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 c82ebc2..8c7de08 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.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 @@
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<DeviceId, ConcurrentMap<GroupKey, StoredGroupEntry>>
groupEntriesByKey = new ConcurrentHashMap<>();
private final ConcurrentMap<DeviceId, ConcurrentMap<GroupId, StoredGroupEntry>>
groupEntriesById = new ConcurrentHashMap<>();
+ private final ConcurrentMap<DeviceId, ConcurrentMap<GroupKey, StoredGroupEntry>>
+ pendingGroupEntriesByKey = new ConcurrentHashMap<>();
+ private final ConcurrentMap<DeviceId, ConcurrentMap<GroupId, Group>>
+ extraneousGroupEntriesById = new ConcurrentHashMap<>();
+
+ private final HashMap<DeviceId, Boolean> deviceAuditStatus =
+ new HashMap<DeviceId, Boolean>();
private final AtomicInteger groupIdGen = new AtomicInteger();
@@ -82,14 +93,26 @@
log.info("Stopped");
}
- private static NewConcurrentHashMap<GroupKey, StoredGroupEntry> lazyEmptyGroupKeyTable() {
+ private static NewConcurrentHashMap<GroupKey, StoredGroupEntry>
+ lazyEmptyGroupKeyTable() {
return NewConcurrentHashMap.<GroupKey, StoredGroupEntry>ifNeeded();
}
- private static NewConcurrentHashMap<GroupId, StoredGroupEntry> lazyEmptyGroupIdTable() {
+ private static NewConcurrentHashMap<GroupId, StoredGroupEntry>
+ lazyEmptyGroupIdTable() {
return NewConcurrentHashMap.<GroupId, StoredGroupEntry>ifNeeded();
}
+ private static NewConcurrentHashMap<GroupKey, StoredGroupEntry>
+ lazyEmptyPendingGroupKeyTable() {
+ return NewConcurrentHashMap.<GroupKey, StoredGroupEntry>ifNeeded();
+ }
+
+ private static NewConcurrentHashMap<GroupId, Group>
+ lazyEmptyExtraneousGroupIdTable() {
+ return NewConcurrentHashMap.<GroupId, Group>ifNeeded();
+ }
+
/**
* Returns the group key table for specified device.
*
@@ -113,6 +136,31 @@
}
/**
+ * 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<GroupKey, StoredGroupEntry>
+ 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<GroupId, Group>
+ getExtraneousGroupIdTable(DeviceId deviceId) {
+ return createIfAbsentUnchecked(extraneousGroupEntriesById,
+ deviceId,
+ lazyEmptyExtraneousGroupIdTable());
+ }
+
+ /**
* Returns the number of groups for the specified device in the store.
*
* @return number of groups for the specified device
@@ -133,20 +181,16 @@
@Override
public Iterable<Group> getGroups(DeviceId deviceId) {
// flatten and make iterator unmodifiable
- if (groupEntriesByKey.get(deviceId) != null) {
- return FluentIterable.from(groupEntriesByKey.get(deviceId).values())
- .transform(
- new Function<StoredGroupEntry, Group>() {
+ return FluentIterable.from(getGroupKeyTable(deviceId).values())
+ .transform(
+ new Function<StoredGroupEntry, Group>() {
- @Override
- public Group apply(
- StoredGroupEntry input) {
- return input;
- }
- });
- } else {
- return null;
- }
+ @Override
+ public Group apply(
+ StoredGroupEntry input) {
+ return input;
+ }
+ });
}
/**
@@ -164,6 +208,30 @@
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 @@
*/
@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<GroupKey, StoredGroupEntry> 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<GroupKey, StoredGroupEntry> keyTable =
getGroupKeyTable(groupDesc.deviceId());
keyTable.put(groupDesc.appCookie(), group);
@@ -198,14 +282,16 @@
* @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 @@
List<GroupBucket> 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 @@
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<GroupKey, StoredGroupEntry> keyTable =
getGroupKeyTable(oldGroup.deviceId());
ConcurrentMap<GroupId, StoredGroupEntry> idTable =
@@ -253,9 +338,8 @@
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 @@
}
}
} 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 @@
@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 @@
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<GroupKey, StoredGroupEntry> 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<GroupId, Group> 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<GroupId, Group> extraneousIdTable =
+ getExtraneousGroupIdTable(group.deviceId());
+ extraneousIdTable.remove(group.id());
+ }
+
+ @Override
+ public Iterable<Group> 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 b1f03f4..277e1ca 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.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 @@
}
}
+ /**
+ * 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 @@
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<Group> createdGroups = simpleGroupStore.getGroups(D1);
int groupCount = 0;
for (Group group:createdGroups) {
@@ -189,7 +207,7 @@
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 @@
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 @@
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<GroupBucket> toRemoveBuckets = new ArrayList<GroupBucket>();
toRemoveBuckets.add(updatedGroupBuckets.buckets().get(0));
@@ -252,23 +265,18 @@
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 @@
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 @@
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 @@
simpleGroupStore.setDelegate(removeGroupEntryDelegate);
simpleGroupStore.removeGroupEntry(existingGroup);
- /* Testing getGroup operation */
+ // Testing getGroup operation
existingGroup = simpleGroupStore.getGroup(D1, removeKey);
assertEquals(null, existingGroup);
- Iterable<Group> 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);