ONOS-895: Group manager implementation

Change-Id: Ie183f722fa39012f8de056961715c325e2388e63
This commit is contained in:
Srikanth Vavilapalli 2015-01-30 12:57:56 -08:00 committed by Ali "The Bomb" Al-Shabibi
parent 0599d51603
commit 45c27c8c62
16 changed files with 1300 additions and 90 deletions

View File

@ -37,7 +37,7 @@ public class DefaultGroupId implements GroupId {
@Override @Override
public int id() { public int id() {
return 0; return this.id;
} }
@Override @Override

View File

@ -17,7 +17,10 @@ package org.onosproject.net.group;
import static org.slf4j.LoggerFactory.getLogger; import static org.slf4j.LoggerFactory.getLogger;
import java.util.Objects;
import org.onosproject.core.GroupId; import org.onosproject.core.GroupId;
import org.onosproject.net.DeviceId;
import org.slf4j.Logger; import org.slf4j.Logger;
/** /**
@ -32,6 +35,7 @@ public class DefaultGroup extends DefaultGroupDescription
private long life; private long life;
private long packets; private long packets;
private long bytes; private long bytes;
private long referenceCount;
private GroupId id; private GroupId id;
/** /**
@ -47,6 +51,29 @@ public class DefaultGroup extends DefaultGroupDescription
this.life = 0; this.life = 0;
this.packets = 0; this.packets = 0;
this.bytes = 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; 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;
}
} }

View File

@ -18,6 +18,8 @@ package org.onosproject.net.group;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Objects;
import org.onosproject.core.GroupId; import org.onosproject.core.GroupId;
import org.onosproject.net.PortNumber; import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.TrafficTreatment; import org.onosproject.net.flow.TrafficTreatment;
@ -178,4 +180,36 @@ public final class DefaultGroupBucket implements GroupBucket {
public GroupId watchGroup() { public GroupId watchGroup() {
return 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;
}
} }

View File

@ -17,6 +17,8 @@ package org.onosproject.net.group;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Objects;
import org.onosproject.core.ApplicationId; import org.onosproject.core.ApplicationId;
import org.onosproject.net.DeviceId; import org.onosproject.net.DeviceId;
@ -49,8 +51,8 @@ public class DefaultGroupDescription implements GroupDescription {
this.type = checkNotNull(type); this.type = checkNotNull(type);
this.deviceId = checkNotNull(deviceId); this.deviceId = checkNotNull(deviceId);
this.buckets = checkNotNull(buckets); this.buckets = checkNotNull(buckets);
this.appCookie = checkNotNull(appCookie); this.appCookie = appCookie;
this.appId = checkNotNull(appId); this.appId = appId;
} }
/** /**
@ -61,11 +63,27 @@ public class DefaultGroupDescription implements GroupDescription {
* *
*/ */
public DefaultGroupDescription(GroupDescription groupDesc) { public DefaultGroupDescription(GroupDescription groupDesc) {
this.type = checkNotNull(groupDesc.type()); this.type = groupDesc.type();
this.deviceId = checkNotNull(groupDesc.deviceId()); this.deviceId = groupDesc.deviceId();
this.buckets = checkNotNull(groupDesc.buckets()); this.buckets = groupDesc.buckets();
this.appCookie = checkNotNull(groupDesc.appCookie()); this.appCookie = groupDesc.appCookie();
this.appId = checkNotNull(groupDesc.appId()); 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; 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;
}
} }

View File

@ -25,6 +25,10 @@ public interface Group extends GroupDescription {
* State of the group object in ONOS. * State of the group object in ONOS.
*/ */
public enum GroupState { 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 * Group create request is processed by ONOS and not yet
* received the confirmation from data plane. * received the confirmation from data plane.
@ -81,4 +85,11 @@ public interface Group extends GroupDescription {
* @return number of bytes * @return number of bytes
*/ */
long 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();
} }

View File

@ -45,4 +45,25 @@ public final class GroupBuckets {
return buckets; 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;
}
} }

View File

@ -17,6 +17,8 @@ package org.onosproject.net.group;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Objects;
import org.onosproject.core.GroupId; import org.onosproject.core.GroupId;
/** /**
@ -142,4 +144,37 @@ public final class GroupOperation {
public GroupBuckets buckets() { public GroupBuckets buckets() {
return this.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;
}
} }

View File

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

View File

@ -16,7 +16,6 @@
package org.onosproject.net.group; package org.onosproject.net.group;
import org.onosproject.core.ApplicationId; import org.onosproject.core.ApplicationId;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId; import org.onosproject.net.DeviceId;
/** /**
@ -27,7 +26,7 @@ import org.onosproject.net.DeviceId;
* specified in a group. * specified in a group.
* "group" can also be used for grouping common actions of different flows, * "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 * 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 * This implements semantics of a distributed authoritative group store
* where the master copy of the groups lies with the controller and * 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 * NOTE1: The presence of group object in the system does not
* guarantee that the "group" is actually created in device. * guarantee that the "group" is actually created in device.
* GROUP_ADDED notification would confirm the creation of * GROUP_ADDED notification would confirm the creation of
* this group in data plane * this group in data plane.
* *
* @param deviceId device identifier * @param deviceId device identifier
* @param appCookie application cookie to be used for lookup * @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 * Appends buckets to existing group. The caller can optionally
* associate a new cookie during this updation. GROUP_UPDATED or * associate a new cookie during this updation. GROUP_UPDATED or
* GROUP_UPDATE_FAILED notifications would be provided along with * 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 deviceId device identifier
* @param oldCookie cookie to be used to retrieve the existing group * @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 * Removes buckets from existing group. The caller can optionally
* associate a new cookie during this updation. GROUP_UPDATED or * associate a new cookie during this updation. GROUP_UPDATED or
* GROUP_UPDATE_FAILED notifications would be provided along with * 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 deviceId device identifier
* @param oldCookie cookie to be used to retrieve the existing group * @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 newCookie immutable cookie to be used post update operation
* @param appId Application Id * @param appId Application Id
*/ */
void removeBucketsFromGroup(Device deviceId, void removeBucketsFromGroup(DeviceId deviceId,
GroupKey oldCookie, GroupKey oldCookie,
GroupBuckets buckets, GroupBuckets buckets,
GroupKey newCookie, GroupKey newCookie,
@ -109,13 +108,13 @@ public interface GroupService {
* Deletes a group associated to an application cookie. * Deletes a group associated to an application cookie.
* GROUP_DELETED or GROUP_DELETE_FAILED notifications would be * GROUP_DELETED or GROUP_DELETE_FAILED notifications would be
* provided along with cookie depending on the result of the * provided along with cookie depending on the result of the
* operation on the device * operation on the device.
* *
* @param deviceId device identifier * @param deviceId device identifier
* @param appCookie application cookie to be used for lookup * @param appCookie application cookie to be used for lookup
* @param appId Application Id * @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 * Retrieves all groups created by an application in the specified device
@ -125,7 +124,7 @@ public interface GroupService {
* @param appId application id * @param appId application id
* @return collection of immutable group objects created by the application * @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. * Adds the specified group listener.

View File

@ -73,12 +73,14 @@ public interface GroupStore extends Store<GroupEvent, GroupStoreDelegate> {
* @param deviceId the device ID * @param deviceId the device ID
* @param oldAppCookie the current group key * @param oldAppCookie the current group key
* @param type update type * @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, void updateGroupDescription(DeviceId deviceId,
GroupKey oldAppCookie, GroupKey oldAppCookie,
UpdateType type, UpdateType type,
GroupDescription newGroupDesc); GroupBuckets newBuckets,
GroupKey newAppCookie);
/** /**
* Triggers deleting the existing group entry. * Triggers deleting the existing group entry.
@ -102,4 +104,43 @@ public interface GroupStore extends Store<GroupEvent, GroupStoreDelegate> {
* @param group group entry * @param group group entry
*/ */
void removeGroupEntry(Group group); 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);
} }

View File

@ -49,4 +49,10 @@ public interface StoredGroupEntry extends Group {
*/ */
void setBytes(long bytes); void setBytes(long bytes);
/**
* Sets number of flow rules or groups referencing this group entry.
*
* @param referenceCount reference count
*/
void setReferenceCount(long referenceCount);
} }

View File

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

View File

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

View File

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

View File

@ -19,6 +19,7 @@ import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsent
import static org.slf4j.LoggerFactory.getLogger; import static org.slf4j.LoggerFactory.getLogger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
@ -62,11 +63,21 @@ public class SimpleGroupStore
private final Logger log = getLogger(getClass()); 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 // inner Map is per device group table
private final ConcurrentMap<DeviceId, ConcurrentMap<GroupKey, StoredGroupEntry>> private final ConcurrentMap<DeviceId, ConcurrentMap<GroupKey, StoredGroupEntry>>
groupEntriesByKey = new ConcurrentHashMap<>(); groupEntriesByKey = new ConcurrentHashMap<>();
private final ConcurrentMap<DeviceId, ConcurrentMap<GroupId, StoredGroupEntry>> private final ConcurrentMap<DeviceId, ConcurrentMap<GroupId, StoredGroupEntry>>
groupEntriesById = new ConcurrentHashMap<>(); 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(); private final AtomicInteger groupIdGen = new AtomicInteger();
@ -82,14 +93,26 @@ public class SimpleGroupStore
log.info("Stopped"); log.info("Stopped");
} }
private static NewConcurrentHashMap<GroupKey, StoredGroupEntry> lazyEmptyGroupKeyTable() { private static NewConcurrentHashMap<GroupKey, StoredGroupEntry>
lazyEmptyGroupKeyTable() {
return NewConcurrentHashMap.<GroupKey, StoredGroupEntry>ifNeeded(); return NewConcurrentHashMap.<GroupKey, StoredGroupEntry>ifNeeded();
} }
private static NewConcurrentHashMap<GroupId, StoredGroupEntry> lazyEmptyGroupIdTable() { private static NewConcurrentHashMap<GroupId, StoredGroupEntry>
lazyEmptyGroupIdTable() {
return NewConcurrentHashMap.<GroupId, StoredGroupEntry>ifNeeded(); 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. * Returns the group key table for specified device.
* *
@ -112,6 +135,31 @@ public class SimpleGroupStore
deviceId, lazyEmptyGroupIdTable()); deviceId, lazyEmptyGroupIdTable());
} }
/**
* Returns the pending group key table for specified device.
*
* @param deviceId identifier of the device
* @return Map representing group key table of given device.
*/
private ConcurrentMap<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. * Returns the number of groups for the specified device in the store.
* *
@ -133,20 +181,16 @@ public class SimpleGroupStore
@Override @Override
public Iterable<Group> getGroups(DeviceId deviceId) { public Iterable<Group> getGroups(DeviceId deviceId) {
// flatten and make iterator unmodifiable // flatten and make iterator unmodifiable
if (groupEntriesByKey.get(deviceId) != null) { return FluentIterable.from(getGroupKeyTable(deviceId).values())
return FluentIterable.from(groupEntriesByKey.get(deviceId).values()) .transform(
.transform( new Function<StoredGroupEntry, Group>() {
new Function<StoredGroupEntry, Group>() {
@Override @Override
public Group apply( public Group apply(
StoredGroupEntry input) { StoredGroupEntry input) {
return input; return input;
} }
}); });
} else {
return null;
}
} }
/** /**
@ -164,6 +208,30 @@ public class SimpleGroupStore
null; 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. * Stores a new group entry using the information from group description.
* *
@ -171,16 +239,32 @@ public class SimpleGroupStore
*/ */
@Override @Override
public void storeGroupDescription(GroupDescription groupDesc) { 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) { if (getGroup(groupDesc.deviceId(), groupDesc.appCookie()) != null) {
return; return;
} }
/* Get a new group identifier */ if (deviceAuditStatus.get(groupDesc.deviceId()) == null) {
GroupId id = new DefaultGroupId(groupIdGen.incrementAndGet()); // Device group audit has not completed yet
/* Create a group entry object */ // 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); 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 = ConcurrentMap<GroupKey, StoredGroupEntry> keyTable =
getGroupKeyTable(groupDesc.deviceId()); getGroupKeyTable(groupDesc.deviceId());
keyTable.put(groupDesc.appCookie(), group); keyTable.put(groupDesc.appCookie(), group);
@ -198,14 +282,16 @@ public class SimpleGroupStore
* @param deviceId the device ID * @param deviceId the device ID
* @param oldAppCookie the current group key * @param oldAppCookie the current group key
* @param type update type * @param type update type
* @param newGroupDesc group description with updates * @param newBuckets group buckets for updates
* @param newAppCookie optional new group key
*/ */
@Override @Override
public void updateGroupDescription(DeviceId deviceId, public void updateGroupDescription(DeviceId deviceId,
GroupKey oldAppCookie, GroupKey oldAppCookie,
UpdateType type, UpdateType type,
GroupDescription newGroupDesc) { GroupBuckets newBuckets,
/* Check if a group is existing with the provided key */ GroupKey newAppCookie) {
// Check if a group is existing with the provided key
Group oldGroup = getGroup(deviceId, oldAppCookie); Group oldGroup = getGroup(deviceId, oldAppCookie);
if (oldGroup == null) { if (oldGroup == null) {
return; return;
@ -213,15 +299,16 @@ public class SimpleGroupStore
List<GroupBucket> newBucketList = getUpdatedBucketList(oldGroup, List<GroupBucket> newBucketList = getUpdatedBucketList(oldGroup,
type, type,
newGroupDesc.buckets()); newBuckets);
if (newBucketList != null) { 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); GroupBuckets updatedBuckets = new GroupBuckets(newBucketList);
GroupKey newCookie = (newAppCookie != null) ? newAppCookie : oldAppCookie;
GroupDescription updatedGroupDesc = new DefaultGroupDescription( GroupDescription updatedGroupDesc = new DefaultGroupDescription(
oldGroup.deviceId(), oldGroup.deviceId(),
oldGroup.type(), oldGroup.type(),
updatedBuckets, updatedBuckets,
newGroupDesc.appCookie(), newCookie,
oldGroup.appId()); oldGroup.appId());
StoredGroupEntry newGroup = new DefaultGroup(oldGroup.id(), StoredGroupEntry newGroup = new DefaultGroup(oldGroup.id(),
updatedGroupDesc); updatedGroupDesc);
@ -229,9 +316,7 @@ public class SimpleGroupStore
newGroup.setLife(oldGroup.life()); newGroup.setLife(oldGroup.life());
newGroup.setPackets(oldGroup.packets()); newGroup.setPackets(oldGroup.packets());
newGroup.setBytes(oldGroup.bytes()); newGroup.setBytes(oldGroup.bytes());
/* Remove the old entry from maps and add new entry // Remove the old entry from maps and add new entry using new key
* using new key
*/
ConcurrentMap<GroupKey, StoredGroupEntry> keyTable = ConcurrentMap<GroupKey, StoredGroupEntry> keyTable =
getGroupKeyTable(oldGroup.deviceId()); getGroupKeyTable(oldGroup.deviceId());
ConcurrentMap<GroupId, StoredGroupEntry> idTable = ConcurrentMap<GroupId, StoredGroupEntry> idTable =
@ -253,9 +338,8 @@ public class SimpleGroupStore
boolean groupDescUpdated = false; boolean groupDescUpdated = false;
if (type == UpdateType.ADD) { if (type == UpdateType.ADD) {
/* Check if the any of the new buckets are part of the // Check if the any of the new buckets are part of
* old bucket list // the old bucket list
*/
for (GroupBucket addBucket:buckets.buckets()) { for (GroupBucket addBucket:buckets.buckets()) {
if (!newBucketList.contains(addBucket)) { if (!newBucketList.contains(addBucket)) {
newBucketList.add(addBucket); newBucketList.add(addBucket);
@ -263,9 +347,8 @@ public class SimpleGroupStore
} }
} }
} else if (type == UpdateType.REMOVE) { } else if (type == UpdateType.REMOVE) {
/* Check if the to be removed buckets are part of the // Check if the to be removed buckets are part of the
* old bucket list // old bucket list
*/
for (GroupBucket removeBucket:buckets.buckets()) { for (GroupBucket removeBucket:buckets.buckets()) {
if (newBucketList.contains(removeBucket)) { if (newBucketList.contains(removeBucket)) {
newBucketList.remove(removeBucket); newBucketList.remove(removeBucket);
@ -290,7 +373,7 @@ public class SimpleGroupStore
@Override @Override
public void deleteGroupDescription(DeviceId deviceId, public void deleteGroupDescription(DeviceId deviceId,
GroupKey appCookie) { 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) ? StoredGroupEntry existing = (groupEntriesByKey.get(deviceId) != null) ?
groupEntriesByKey.get(deviceId).get(appCookie) : groupEntriesByKey.get(deviceId).get(appCookie) :
null; null;
@ -362,4 +445,56 @@ public class SimpleGroupStore
notifyDelegate(new GroupEvent(Type.GROUP_REMOVED, existing)); 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());
}
} }

View File

@ -40,8 +40,10 @@ import org.onosproject.net.group.GroupBuckets;
import org.onosproject.net.group.GroupDescription; import org.onosproject.net.group.GroupDescription;
import org.onosproject.net.group.GroupEvent; import org.onosproject.net.group.GroupEvent;
import org.onosproject.net.group.GroupKey; import org.onosproject.net.group.GroupKey;
import org.onosproject.net.group.GroupStoreDelegate;
import org.onosproject.net.group.GroupStore.UpdateType; 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. * 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 @Test
public void testGroupStoreOperations() { public void testGroupStoreOperations() {
// Set the Device AUDIT completed in the store
simpleGroupStore.deviceInitialAuditCompleted(D1);
ApplicationId appId = ApplicationId appId =
new DefaultApplicationId(2, "org.groupstore.test"); new DefaultApplicationId(2, "org.groupstore.test");
TestGroupKey key = new TestGroupKey("group1"); TestGroupKey key = new TestGroupKey("group1");
@ -169,17 +187,17 @@ public class SimpleGroupStoreTest {
groupBuckets, groupBuckets,
GroupEvent.Type.GROUP_ADD_REQUESTED); GroupEvent.Type.GROUP_ADD_REQUESTED);
simpleGroupStore.setDelegate(checkStoreGroupDelegate); simpleGroupStore.setDelegate(checkStoreGroupDelegate);
/* Testing storeGroup operation */ // Testing storeGroup operation
simpleGroupStore.storeGroupDescription(groupDesc); simpleGroupStore.storeGroupDescription(groupDesc);
/* Testing getGroupCount operation */ // Testing getGroupCount operation
assertEquals(1, simpleGroupStore.getGroupCount(D1)); assertEquals(1, simpleGroupStore.getGroupCount(D1));
/* Testing getGroup operation */ // Testing getGroup operation
Group createdGroup = simpleGroupStore.getGroup(D1, key); Group createdGroup = simpleGroupStore.getGroup(D1, key);
checkStoreGroupDelegate.verifyGroupId(createdGroup.id()); checkStoreGroupDelegate.verifyGroupId(createdGroup.id());
/* Testing getGroups operation */ // Testing getGroups operation
Iterable<Group> createdGroups = simpleGroupStore.getGroups(D1); Iterable<Group> createdGroups = simpleGroupStore.getGroups(D1);
int groupCount = 0; int groupCount = 0;
for (Group group:createdGroups) { for (Group group:createdGroups) {
@ -189,7 +207,7 @@ public class SimpleGroupStoreTest {
assertEquals(1, groupCount); assertEquals(1, groupCount);
simpleGroupStore.unsetDelegate(checkStoreGroupDelegate); simpleGroupStore.unsetDelegate(checkStoreGroupDelegate);
/* Testing addOrUpdateGroupEntry operation from southbound */ // Testing addOrUpdateGroupEntry operation from southbound
InternalGroupStoreDelegate addGroupEntryDelegate = InternalGroupStoreDelegate addGroupEntryDelegate =
new InternalGroupStoreDelegate(key, new InternalGroupStoreDelegate(key,
groupBuckets, groupBuckets,
@ -198,7 +216,7 @@ public class SimpleGroupStoreTest {
simpleGroupStore.addOrUpdateGroupEntry(createdGroup); simpleGroupStore.addOrUpdateGroupEntry(createdGroup);
simpleGroupStore.unsetDelegate(addGroupEntryDelegate); simpleGroupStore.unsetDelegate(addGroupEntryDelegate);
/* Testing updateGroupDescription for ADD operation from northbound */ // Testing updateGroupDescription for ADD operation from northbound
TestGroupKey addKey = new TestGroupKey("group1AddBuckets"); TestGroupKey addKey = new TestGroupKey("group1AddBuckets");
PortNumber[] newNeighborPorts = {PortNumber.portNumber(41), PortNumber[] newNeighborPorts = {PortNumber.portNumber(41),
PortNumber.portNumber(42)}; PortNumber.portNumber(42)};
@ -225,19 +243,14 @@ public class SimpleGroupStoreTest {
updatedGroupBuckets, updatedGroupBuckets,
GroupEvent.Type.GROUP_UPDATE_REQUESTED); GroupEvent.Type.GROUP_UPDATE_REQUESTED);
simpleGroupStore.setDelegate(updateGroupDescDelegate); simpleGroupStore.setDelegate(updateGroupDescDelegate);
GroupDescription newGroupDesc = new DefaultGroupDescription(
D1,
Group.Type.SELECT,
toAddGroupBuckets,
addKey,
appId);
simpleGroupStore.updateGroupDescription(D1, simpleGroupStore.updateGroupDescription(D1,
key, key,
UpdateType.ADD, UpdateType.ADD,
newGroupDesc); toAddGroupBuckets,
addKey);
simpleGroupStore.unsetDelegate(updateGroupDescDelegate); simpleGroupStore.unsetDelegate(updateGroupDescDelegate);
/* Testing updateGroupDescription for REMOVE operation from northbound */ // Testing updateGroupDescription for REMOVE operation from northbound
TestGroupKey removeKey = new TestGroupKey("group1RemoveBuckets"); TestGroupKey removeKey = new TestGroupKey("group1RemoveBuckets");
List<GroupBucket> toRemoveBuckets = new ArrayList<GroupBucket>(); List<GroupBucket> toRemoveBuckets = new ArrayList<GroupBucket>();
toRemoveBuckets.add(updatedGroupBuckets.buckets().get(0)); toRemoveBuckets.add(updatedGroupBuckets.buckets().get(0));
@ -252,23 +265,18 @@ public class SimpleGroupStoreTest {
remainingGroupBuckets, remainingGroupBuckets,
GroupEvent.Type.GROUP_UPDATE_REQUESTED); GroupEvent.Type.GROUP_UPDATE_REQUESTED);
simpleGroupStore.setDelegate(removeGroupDescDelegate); simpleGroupStore.setDelegate(removeGroupDescDelegate);
GroupDescription removeGroupDesc = new DefaultGroupDescription(
D1,
Group.Type.SELECT,
toRemoveGroupBuckets,
removeKey,
appId);
simpleGroupStore.updateGroupDescription(D1, simpleGroupStore.updateGroupDescription(D1,
addKey, addKey,
UpdateType.REMOVE, UpdateType.REMOVE,
removeGroupDesc); toRemoveGroupBuckets,
removeKey);
simpleGroupStore.unsetDelegate(removeGroupDescDelegate); simpleGroupStore.unsetDelegate(removeGroupDescDelegate);
/* Testing getGroup operation */ // Testing getGroup operation
Group existingGroup = simpleGroupStore.getGroup(D1, removeKey); Group existingGroup = simpleGroupStore.getGroup(D1, removeKey);
checkStoreGroupDelegate.verifyGroupId(existingGroup.id()); checkStoreGroupDelegate.verifyGroupId(existingGroup.id());
/* Testing addOrUpdateGroupEntry operation from southbound */ // Testing addOrUpdateGroupEntry operation from southbound
InternalGroupStoreDelegate updateGroupEntryDelegate = InternalGroupStoreDelegate updateGroupEntryDelegate =
new InternalGroupStoreDelegate(removeKey, new InternalGroupStoreDelegate(removeKey,
remainingGroupBuckets, remainingGroupBuckets,
@ -277,7 +285,7 @@ public class SimpleGroupStoreTest {
simpleGroupStore.addOrUpdateGroupEntry(existingGroup); simpleGroupStore.addOrUpdateGroupEntry(existingGroup);
simpleGroupStore.unsetDelegate(updateGroupEntryDelegate); simpleGroupStore.unsetDelegate(updateGroupEntryDelegate);
/* Testing deleteGroupDescription operation from northbound */ // Testing deleteGroupDescription operation from northbound
InternalGroupStoreDelegate deleteGroupDescDelegate = InternalGroupStoreDelegate deleteGroupDescDelegate =
new InternalGroupStoreDelegate(removeKey, new InternalGroupStoreDelegate(removeKey,
remainingGroupBuckets, remainingGroupBuckets,
@ -286,7 +294,7 @@ public class SimpleGroupStoreTest {
simpleGroupStore.deleteGroupDescription(D1, removeKey); simpleGroupStore.deleteGroupDescription(D1, removeKey);
simpleGroupStore.unsetDelegate(deleteGroupDescDelegate); simpleGroupStore.unsetDelegate(deleteGroupDescDelegate);
/* Testing removeGroupEntry operation from southbound */ // Testing removeGroupEntry operation from southbound
InternalGroupStoreDelegate removeGroupEntryDelegate = InternalGroupStoreDelegate removeGroupEntryDelegate =
new InternalGroupStoreDelegate(removeKey, new InternalGroupStoreDelegate(removeKey,
remainingGroupBuckets, remainingGroupBuckets,
@ -294,17 +302,10 @@ public class SimpleGroupStoreTest {
simpleGroupStore.setDelegate(removeGroupEntryDelegate); simpleGroupStore.setDelegate(removeGroupEntryDelegate);
simpleGroupStore.removeGroupEntry(existingGroup); simpleGroupStore.removeGroupEntry(existingGroup);
/* Testing getGroup operation */ // Testing getGroup operation
existingGroup = simpleGroupStore.getGroup(D1, removeKey); existingGroup = simpleGroupStore.getGroup(D1, removeKey);
assertEquals(null, existingGroup); assertEquals(null, existingGroup);
Iterable<Group> existingGroups = simpleGroupStore.getGroups(D1); assertEquals(0, Iterables.size(simpleGroupStore.getGroups(D1)));
groupCount = 0;
for (Group tmp:existingGroups) {
/* To avoid warning */
assertEquals(null, tmp);
groupCount++;
}
assertEquals(0, groupCount);
assertEquals(0, simpleGroupStore.getGroupCount(D1)); assertEquals(0, simpleGroupStore.getGroupCount(D1));
simpleGroupStore.unsetDelegate(removeGroupEntryDelegate); simpleGroupStore.unsetDelegate(removeGroupEntryDelegate);