Srikanth Vavilapalli
Committed by Ali "The Bomb" Al-Shabibi

ONOS-895: Group manager implementation

Change-Id: Ie183f722fa39012f8de056961715c325e2388e63
......@@ -37,7 +37,7 @@ public class DefaultGroupId implements GroupId {
@Override
public int id() {
return 0;
return this.id;
}
@Override
......
......@@ -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;
}
}
......
......@@ -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;
}
}
......
......@@ -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
......
......@@ -26,6 +26,10 @@ public interface Group extends GroupDescription {
*/
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();
}
......
......@@ -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
......
......@@ -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
......
/*
* 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> {
}
......@@ -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<Group> getGroups(Device deviceId, ApplicationId appId);
Iterable<Group> getGroups(DeviceId deviceId, ApplicationId appId);
/**
* Adds the specified group listener.
......
......@@ -73,12 +73,14 @@ public interface GroupStore extends Store<GroupEvent, GroupStoreDelegate> {
* @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<GroupEvent, GroupStoreDelegate> {
* @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);
}
......
......@@ -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);
}
......
/*
* 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);
}
}
}
}
/*
* 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
/*
* 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;
}
}
}
......@@ -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<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 @@ public class SimpleGroupStore
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 @@ public class SimpleGroupStore
}
/**
* 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,8 +181,7 @@ public class SimpleGroupStore
@Override
public Iterable<Group> getGroups(DeviceId deviceId) {
// flatten and make iterator unmodifiable
if (groupEntriesByKey.get(deviceId) != null) {
return FluentIterable.from(groupEntriesByKey.get(deviceId).values())
return FluentIterable.from(getGroupKeyTable(deviceId).values())
.transform(
new Function<StoredGroupEntry, Group>() {
......@@ -144,9 +191,6 @@ public class SimpleGroupStore
return input;
}
});
} else {
return null;
}
}
/**
......@@ -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<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 @@ 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<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 @@ 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<GroupKey, StoredGroupEntry> keyTable =
getGroupKeyTable(oldGroup.deviceId());
ConcurrentMap<GroupId, StoredGroupEntry> 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<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());
}
}
......
......@@ -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<Group> 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<GroupBucket> toRemoveBuckets = new ArrayList<GroupBucket>();
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<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);
......