From ed12ae5e81d0da5b2192667d0f2ded93995b8afc Mon Sep 17 00:00:00 2001 From: Srikanth Vavilapalli Date: Mon, 9 Feb 2015 14:43:19 -0800 Subject: [PATCH] ONOS-985: Sample integration test application for group subsystem Change-Id: I68352f922e5c7a0800fcc4fa839955769bf925a6 --- apps/grouphandler/pom.xml | 72 ++++ .../grouphandler/DefaultEdgeGroupHandler.java | 174 ++++++++ .../grouphandler/DefaultGroupHandler.java | 388 ++++++++++++++++++ .../grouphandler/DefaultGroupHandlerApp.java | 208 ++++++++++ .../DefaultTransitGroupHandler.java | 177 ++++++++ .../grouphandler/DeviceProperties.java | 57 +++ .../grouphandler/GroupBucketIdentifier.java | 70 ++++ .../onosproject/grouphandler/NeighborSet.java | 123 ++++++ .../grouphandler/PolicyGroupHandler.java | 345 ++++++++++++++++ .../grouphandler/PolicyGroupIdentifier.java | 92 +++++ .../grouphandler/PolicyGroupParams.java | 92 +++++ .../onosproject/net/group/DefaultGroup.java | 10 + .../net/group/DefaultGroupBucket.java | 8 + .../net/group/DefaultGroupDescription.java | 10 + .../onosproject/net/group/GroupBuckets.java | 7 + .../org/onosproject/net/group/GroupStore.java | 3 +- .../net/group/impl/GroupManager.java | 72 +++- .../net/group/impl/GroupManagerTest.java | 123 ++++-- .../store/trivial/impl/SimpleGroupStore.java | 47 ++- .../trivial/impl/SimpleGroupStoreTest.java | 144 ++++--- 20 files changed, 2126 insertions(+), 96 deletions(-) create mode 100644 apps/grouphandler/pom.xml create mode 100644 apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultEdgeGroupHandler.java create mode 100644 apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultGroupHandler.java create mode 100644 apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultGroupHandlerApp.java create mode 100644 apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultTransitGroupHandler.java create mode 100644 apps/grouphandler/src/main/java/org/onosproject/grouphandler/DeviceProperties.java create mode 100644 apps/grouphandler/src/main/java/org/onosproject/grouphandler/GroupBucketIdentifier.java create mode 100644 apps/grouphandler/src/main/java/org/onosproject/grouphandler/NeighborSet.java create mode 100644 apps/grouphandler/src/main/java/org/onosproject/grouphandler/PolicyGroupHandler.java create mode 100644 apps/grouphandler/src/main/java/org/onosproject/grouphandler/PolicyGroupIdentifier.java create mode 100644 apps/grouphandler/src/main/java/org/onosproject/grouphandler/PolicyGroupParams.java diff --git a/apps/grouphandler/pom.xml b/apps/grouphandler/pom.xml new file mode 100644 index 0000000000..a245b9ff54 --- /dev/null +++ b/apps/grouphandler/pom.xml @@ -0,0 +1,72 @@ + + + + 4.0.0 + + + org.onosproject + onos-apps + 1.1.0-SNAPSHOT + ../pom.xml + + + onos-app-grouphandler + bundle + + ONOS sample application using group service + + + + org.onosproject + onos-api + ${project.version} + + + org.onosproject + onlab-osgi + ${project.version} + + + org.onosproject + onlab-nio + ${project.version} + + + org.onosproject + onlab-netty + ${project.version} + + + org.apache.karaf.shell + org.apache.karaf.shell.console + + + org.onosproject + onlab-misc + + + com.google.guava + guava + + + org.osgi + org.osgi.core + + + diff --git a/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultEdgeGroupHandler.java b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultEdgeGroupHandler.java new file mode 100644 index 0000000000..d5d489c287 --- /dev/null +++ b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultEdgeGroupHandler.java @@ -0,0 +1,174 @@ +/* + * 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.grouphandler; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.onosproject.core.ApplicationId; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.group.DefaultGroupBucket; +import org.onosproject.net.group.GroupBucket; +import org.onosproject.net.group.GroupBuckets; +import org.onosproject.net.group.GroupService; +import org.onosproject.net.link.LinkService; + +/** + * Default ECMP group handler creation module for an edge device. + * This component creates a set of ECMP groups for every neighbor + * that this device is connected to. + * For example, consider a network of 4 devices: D0 (Segment ID: 100), + * D1 (Segment ID: 101), D2 (Segment ID: 102) and D3 (Segment ID: 103), + * where D0 and D3 are edge devices and D1 and D2 are transit devices. + * Assume device D0 is connected to 2 neighbors (D1 and D2 ). + * The following groups will be created in D0: + * 1) all ports to D1 + with no label push, + * 2) all ports to D1 + with label 102 pushed, + * 3) all ports to D1 + with label 103 pushed, + * 4) all ports to D2 + with no label push, + * 5) all ports to D2 + with label 101 pushed, + * 6) all ports to D2 + with label 103 pushed, + * 7) all ports to D1 and D2 + with label 103 pushed + */ +public class DefaultEdgeGroupHandler extends DefaultGroupHandler { + + protected DefaultEdgeGroupHandler(DeviceId deviceId, + ApplicationId appId, + DeviceProperties config, + LinkService linkService, + GroupService groupService) { + super(deviceId, appId, config, linkService, groupService); + } + + @Override + public void createGroups() { + log.debug("Creating default groups " + + "for edge device {}", deviceId); + Set neighbors = devicePortMap.keySet(); + if (neighbors == null || neighbors.isEmpty()) { + return; + } + + // Create all possible Neighbor sets from this router + Set> powerSet = getPowerSetOfNeighbors(neighbors); + log.trace("createGroupsAtEdgeRouter: The size of neighbor powerset " + + "for sw {} is {}", deviceId, powerSet.size()); + Set nsSet = new HashSet(); + for (Set combo : powerSet) { + if (combo.isEmpty()) { + continue; + } + List groupSegmentIds = + getSegmentIdsTobePairedWithNeighborSet(combo); + for (Integer sId : groupSegmentIds) { + NeighborSet ns = new NeighborSet(combo, sId); + log.trace("createGroupsAtEdgeRouter: sw {} " + + "combo {} sId {} ns {}", + deviceId, combo, sId, ns); + nsSet.add(ns); + } + } + log.trace("createGroupsAtEdgeRouter: The neighborset " + + "with label for sw {} is {}", + deviceId, nsSet); + + createGroupsFromNeighborsets(nsSet); + } + + @Override + protected void newNeighbor(Link newNeighborLink) { + log.debug("New Neighbor: Updating groups " + + "for edge device {}", deviceId); + // Recompute neighbor power set + addNeighborAtPort(newNeighborLink.dst().deviceId(), + newNeighborLink.src().port()); + // Compute new neighbor sets due to the addition of new neighbor + Set nsSet = computeImpactedNeighborsetForPortEvent( + newNeighborLink.dst().deviceId(), + devicePortMap.keySet()); + createGroupsFromNeighborsets(nsSet); + } + + @Override + protected void newPortToExistingNeighbor(Link newNeighborLink) { + log.debug("New port to existing neighbor: Updating " + + "groups for edge device {}", deviceId); + addNeighborAtPort(newNeighborLink.dst().deviceId(), + newNeighborLink.src().port()); + Set nsSet = computeImpactedNeighborsetForPortEvent( + newNeighborLink.dst().deviceId(), + devicePortMap.keySet()); + for (NeighborSet ns : nsSet) { + // Create the new bucket to be updated + TrafficTreatment.Builder tBuilder = + DefaultTrafficTreatment.builder(); + tBuilder.setOutput(newNeighborLink.src().port()) + .setEthDst(deviceConfig.getDeviceMac( + newNeighborLink.dst().deviceId())) + .setEthSrc(nodeMacAddr) + .pushMpls() + .setMpls(ns.getEdgeLabel()); + GroupBucket updatedBucket = DefaultGroupBucket. + createSelectGroupBucket(tBuilder.build()); + GroupBuckets updatedBuckets = new GroupBuckets( + Arrays.asList(updatedBucket)); + log.debug("newPortToExistingNeighborAtEdgeRouter: " + + "groupService.addBucketsToGroup for neighborset{}", ns); + groupService.addBucketsToGroup(deviceId, ns, updatedBuckets, ns, appId); + } + } + + @Override + protected Set computeImpactedNeighborsetForPortEvent( + DeviceId impactedNeighbor, + Set updatedNeighbors) { + Set> powerSet = getPowerSetOfNeighbors(updatedNeighbors); + + Set tmp = new HashSet(); + tmp.addAll(updatedNeighbors); + tmp.remove(impactedNeighbor); + Set> tmpPowerSet = getPowerSetOfNeighbors(tmp); + + // Compute the impacted neighbor sets + powerSet.removeAll(tmpPowerSet); + + Set nsSet = new HashSet(); + for (Set combo : powerSet) { + if (combo.isEmpty()) { + continue; + } + List groupSegmentIds = + getSegmentIdsTobePairedWithNeighborSet(combo); + for (Integer sId : groupSegmentIds) { + NeighborSet ns = new NeighborSet(combo, sId); + log.trace("computeImpactedNeighborsetForPortEvent: sw {} " + + "combo {} sId {} ns {}", + deviceId, combo, sId, ns); + nsSet.add(ns); + } + } + log.trace("computeImpactedNeighborsetForPortEvent: The neighborset " + + "with label for sw {} is {}", + deviceId, nsSet); + return nsSet; + } + +} diff --git a/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultGroupHandler.java b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultGroupHandler.java new file mode 100644 index 0000000000..1421f85bcb --- /dev/null +++ b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultGroupHandler.java @@ -0,0 +1,388 @@ +/* + * 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.grouphandler; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.onlab.packet.MacAddress; +import org.onosproject.core.ApplicationId; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficTreatment; +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.GroupService; +import org.onosproject.net.link.LinkService; +import org.slf4j.Logger; + +/** + * Default ECMP group handler creation module. This + * component creates a set of ECMP groups for every neighbor + * that this device is connected to based on whether the + * current device is an edge device or a transit device. + */ +public class DefaultGroupHandler { + protected final Logger log = getLogger(getClass()); + + protected final DeviceId deviceId; + protected final ApplicationId appId; + protected final DeviceProperties deviceConfig; + protected final List allSegmentIds; + protected final int nodeSegmentId; + protected final boolean isEdgeRouter; + protected final MacAddress nodeMacAddr; + protected LinkService linkService; + protected GroupService groupService; + + protected HashMap> devicePortMap = + new HashMap>(); + protected HashMap portDeviceMap = + new HashMap(); + + private GroupListener listener = new InternalGroupListener(); + + protected DefaultGroupHandler(DeviceId deviceId, + ApplicationId appId, + DeviceProperties config, + LinkService linkService, + GroupService groupService) { + this.deviceId = checkNotNull(deviceId); + this.appId = checkNotNull(appId); + this.deviceConfig = checkNotNull(config); + this.linkService = checkNotNull(linkService); + this.groupService = checkNotNull(groupService); + allSegmentIds = checkNotNull(config.getAllDeviceSegmentIds()); + nodeSegmentId = config.getSegmentId(deviceId); + isEdgeRouter = config.isEdgeDevice(deviceId); + nodeMacAddr = checkNotNull(config.getDeviceMac(deviceId)); + + this.groupService.addListener(listener); + + populateNeighborMaps(); + } + + /** + * Creates a group handler object based on the type of device. If + * device is of edge type it returns edge group handler, else it + * returns transit group handler. + * + * @param deviceId device identifier + * @param appId application identifier + * @param config interface to retrieve the device properties + * @param linkService link service object + * @param groupService group service object + * @return default group handler type + */ + public static DefaultGroupHandler createGroupHandler(DeviceId deviceId, + ApplicationId appId, + DeviceProperties config, + LinkService linkService, + GroupService groupService) { + if (config.isEdgeDevice(deviceId)) { + return new DefaultEdgeGroupHandler(deviceId, + appId, + config, + linkService, + groupService); + } else { + return new DefaultTransitGroupHandler(deviceId, + appId, + config, + linkService, + groupService); + } + } + + /** + * Creates the auto created groups for this device based on the + * current snapshot of the topology. + */ + //Empty implementations to be overridden by derived classes + public void createGroups() { + } + + /** + * Performs group creation or update procedures when a new link + * is discovered on this device. + * + * @param newLink new neighbor link + */ + public void linkUp(Link newLink) { + if (newLink.type() != Link.Type.DIRECT) { + log.warn("linkUp: unknown link type"); + return; + } + + if (!newLink.src().deviceId().equals(deviceId)) { + log.warn("linkUp: deviceId{} doesn't match with link src{}", + deviceId, + newLink.src().deviceId()); + return; + } + + log.debug("Device {} linkUp at local port {} to neighbor {}", + deviceId, newLink.src().port(), newLink.dst().deviceId()); + if (devicePortMap.get(newLink.dst().deviceId()) == null) { + // New Neighbor + newNeighbor(newLink); + } else { + // Old Neighbor + newPortToExistingNeighbor(newLink); + } + } + + /** + * Performs group recovery procedures when a port goes down + * on this device. + * + * @param port port number that has gone down + */ + public void portDown(PortNumber port) { + if (portDeviceMap.get(port) == null) { + log.warn("portDown: unknown port"); + return; + } + log.debug("Device {} portDown {} to neighbor {}", + deviceId, port, portDeviceMap.get(port)); + Set nsSet = computeImpactedNeighborsetForPortEvent( + portDeviceMap.get(port), + devicePortMap.keySet()); + for (NeighborSet ns : nsSet) { + // Create the bucket to be removed + TrafficTreatment.Builder tBuilder = + DefaultTrafficTreatment.builder(); + tBuilder.setOutput(port) + .setEthDst(deviceConfig.getDeviceMac( + portDeviceMap.get(port))) + .setEthSrc(nodeMacAddr) + .pushMpls() + .setMpls(ns.getEdgeLabel()); + GroupBucket removeBucket = DefaultGroupBucket. + createSelectGroupBucket(tBuilder.build()); + GroupBuckets removeBuckets = new GroupBuckets( + Arrays.asList(removeBucket)); + log.debug("portDown in device{}: " + + "groupService.removeBucketsFromGroup " + + "for neighborset{}", deviceId, ns); + groupService.removeBucketsFromGroup(deviceId, + ns, + removeBuckets, + ns, + appId); + } + + devicePortMap.get(portDeviceMap.get(port)).remove(port); + portDeviceMap.remove(port); + } + + /** + * Returns a group associated with the key. + * + * @param key cookie associated with the group + * @return group if found or null + */ + public Group getGroup(GroupKey key) { + return groupService.getGroup(deviceId, key); + } + + //Empty implementation + protected void newNeighbor(Link newLink) { + } + + //Empty implementation + protected void newPortToExistingNeighbor(Link newLink) { + } + + //Empty implementation + protected Set computeImpactedNeighborsetForPortEvent( + DeviceId impactedNeighbor, + Set updatedNeighbors) { + return null; + } + + private void populateNeighborMaps() { + Set outgoingLinks = linkService.getDeviceEgressLinks(deviceId); + for (Link link:outgoingLinks) { + if (link.type() != Link.Type.DIRECT) { + continue; + } + addNeighborAtPort(link.dst().deviceId(), link.src().port()); + } + } + + protected void addNeighborAtPort(DeviceId neighborId, PortNumber portToNeighbor) { + // Update DeviceToPort database + log.debug("Device {} addNeighborAtPort: neighbor {} at port {}", + deviceId, neighborId, portToNeighbor); + if (devicePortMap.get(neighborId) != null) { + devicePortMap.get(neighborId).add(portToNeighbor); + } else { + Set ports = new HashSet(); + ports.add(portToNeighbor); + devicePortMap.put(neighborId, ports); + } + + // Update portToDevice database + if (portDeviceMap.get(portToNeighbor) == null) { + portDeviceMap.put(portToNeighbor, neighborId); + } + } + + protected Set> + getPowerSetOfNeighbors(Set neighbors) { + List list = new ArrayList(neighbors); + Set> sets = new HashSet>(); + // get the number of elements in the neighbors + int elements = list.size(); + // the number of members of a power set is 2^n + // including the empty set + int powerElements = (1 << elements); + + // run a binary counter for the number of power elements + // NOTE: Exclude empty set + for (long i = 1; i < powerElements; i++) { + Set neighborSubSet = new HashSet(); + for (int j = 0; j < elements; j++) { + if ((i >> j) % 2 == 1) { + neighborSubSet.add(list.get(j)); + } + } + sets.add(neighborSubSet); + } + return sets; + } + + private boolean isSegmentIdSameAsNodeSegmentId(DeviceId deviceId, int sId) { + return (deviceConfig.getSegmentId(deviceId) == sId); + } + + protected List getSegmentIdsTobePairedWithNeighborSet( + Set neighbors) { + + List nsSegmentIds = new ArrayList(); + + // Add one entry for "no label" (-1) to the list if + // dpid list has not more than one node/neighbor as + // there will never be a case a packet going to more than one + // neighbor without a label at an edge router + if (neighbors.size() == 1) { + nsSegmentIds.add(-1); + } + // Filter out SegmentIds matching with the + // nodes in the combo + for (Integer sId : allSegmentIds) { + if (sId.equals(nodeSegmentId)) { + continue; + } + boolean filterOut = false; + // Check if the edge label being set is of + // any node in the Neighbor set + for (DeviceId deviceId : neighbors) { + if (isSegmentIdSameAsNodeSegmentId(deviceId, sId)) { + filterOut = true; + break; + } + } + if (!filterOut) { + nsSegmentIds.add(sId); + } + } + return nsSegmentIds; + } + + protected void createGroupsFromNeighborsets(Set nsSet) { + for (NeighborSet ns : nsSet) { + // Create the bucket array from the neighbor set + List buckets = new ArrayList(); + for (DeviceId d : ns.getDeviceIds()) { + for (PortNumber sp : devicePortMap.get(d)) { + TrafficTreatment.Builder tBuilder = + DefaultTrafficTreatment.builder(); + tBuilder.setOutput(sp) + .setEthDst(deviceConfig.getDeviceMac(d)) + .setEthSrc(nodeMacAddr) + .pushMpls() + .setMpls(ns.getEdgeLabel()); + buckets.add(DefaultGroupBucket.createSelectGroupBucket( + tBuilder.build())); + } + } + GroupBuckets groupBuckets = new GroupBuckets(buckets); + GroupDescription newGroupDesc = new DefaultGroupDescription( + deviceId, + Group.Type.SELECT, + groupBuckets, + ns, + appId); + log.debug("createGroupsFromNeighborsets: " + + "groupService.addGroup for neighborset{}", ns); + groupService.addGroup(newGroupDesc); + } + } + + protected void handleGroupEvent(GroupEvent event) { + switch (event.type()) { + case GROUP_ADDED: + log.debug("Received GROUP_ADDED from group service " + + "for device {} with group key{} with id{}", + event.subject().deviceId(), + event.subject().appCookie(), + event.subject().id()); + break; + case GROUP_UPDATED: + log.trace("Received GROUP_UPDATED from group service " + + "for device {} with group key{} with id{}", + event.subject().deviceId(), + event.subject().appCookie(), + event.subject().id()); + break; + case GROUP_REMOVED: + log.debug("Received GROUP_REMOVED from group service " + + "for device {} with group key{} with id{}", + event.subject().deviceId(), + event.subject().appCookie(), + event.subject().id()); + break; + default: + break; + } + } + + private class InternalGroupListener implements GroupListener { + + @Override + public void event(GroupEvent event) { + handleGroupEvent(event); + } + } +} diff --git a/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultGroupHandlerApp.java b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultGroupHandlerApp.java new file mode 100644 index 0000000000..2bd2b4ea69 --- /dev/null +++ b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultGroupHandlerApp.java @@ -0,0 +1,208 @@ +/* + * 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.grouphandler; + +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +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.onlab.packet.MacAddress; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.group.GroupService; +import org.onosproject.net.link.LinkEvent; +import org.onosproject.net.link.LinkListener; +import org.onosproject.net.link.LinkService; +import org.onosproject.net.topology.TopologyService; +import org.slf4j.Logger; + +/** + * Sample application to verify group subsystem end to end. + * This application expects a network of maximum of six connected + * devices for the test to work. For every device in the network, + * this test application launches a default group handler function + * that creates ECMP groups for every neighbor the device is + * connected to. + */ +@Component(immediate = true) +public class DefaultGroupHandlerApp { + + private final Logger log = getLogger(getClass()); + + private final DeviceProperties config = new DeviceConfiguration(); + private ApplicationId appId; + private HashMap dghMap = + new HashMap(); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected TopologyService topologyService; + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected LinkService linkService; + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected GroupService groupService; + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + private DeviceListener deviceListener = new InternalDeviceListener(); + private LinkListener linkListener = new InternalLinkListener(); + + @Activate + public void activate() { + appId = coreService.registerApplication("org.onosproject.defaultgrouphandler"); + log.info("DefaultGroupHandlerApp Activating"); + deviceService.addListener(deviceListener); + linkService.addListener(linkListener); + for (Device device: deviceService.getDevices()) { + log.debug("Initiating default group handling for {}", device.id()); + DefaultGroupHandler dgh = DefaultGroupHandler.createGroupHandler(device.id(), + appId, + config, + linkService, + groupService); + dgh.createGroups(); + dghMap.put(device.id(), dgh); + } + log.info("Activated"); + } + + @Deactivate + public void deactivate() { + dghMap.clear(); + } + + public class DeviceConfiguration implements DeviceProperties { + private final List allSegmentIds = + Arrays.asList(101, 102, 103, 104, 105, 106); + private HashMap deviceSegmentIdMap = + new HashMap() { + { + put(DeviceId.deviceId("of:0000000000000001"), 101); + put(DeviceId.deviceId("of:0000000000000002"), 102); + put(DeviceId.deviceId("of:0000000000000003"), 103); + put(DeviceId.deviceId("of:0000000000000004"), 104); + put(DeviceId.deviceId("of:0000000000000005"), 105); + put(DeviceId.deviceId("of:0000000000000006"), 106); + } + }; + private final HashMap deviceMacMap = + new HashMap() { + { + put(DeviceId.deviceId("of:0000000000000001"), + MacAddress.valueOf("00:00:00:00:00:01")); + put(DeviceId.deviceId("of:0000000000000002"), + MacAddress.valueOf("00:00:00:00:00:02")); + put(DeviceId.deviceId("of:0000000000000003"), + MacAddress.valueOf("00:00:00:00:00:03")); + put(DeviceId.deviceId("of:0000000000000004"), + MacAddress.valueOf("00:00:00:00:00:04")); + put(DeviceId.deviceId("of:0000000000000005"), + MacAddress.valueOf("00:00:00:00:00:05")); + put(DeviceId.deviceId("of:0000000000000006"), + MacAddress.valueOf("00:00:00:00:00:06")); + } + }; + + @Override + public int getSegmentId(DeviceId deviceId) { + if (deviceSegmentIdMap.get(deviceId) != null) { + log.debug("getSegmentId for device{} is {}", + deviceId, + deviceSegmentIdMap.get(deviceId)); + return deviceSegmentIdMap.get(deviceId); + } else { + throw new IllegalStateException(); + } + } + @Override + public MacAddress getDeviceMac(DeviceId deviceId) { + if (deviceMacMap.get(deviceId) != null) { + log.debug("getDeviceMac for device{} is {}", + deviceId, + deviceMacMap.get(deviceId)); + return deviceMacMap.get(deviceId); + } else { + throw new IllegalStateException(); + } + } + @Override + public boolean isEdgeDevice(DeviceId deviceId) { + return true; + } + @Override + public List getAllDeviceSegmentIds() { + return allSegmentIds; + } + } + + private class InternalDeviceListener implements DeviceListener { + + @Override + public void event(DeviceEvent event) { + switch (event.type()) { + case DEVICE_ADDED: + log.debug("Initiating default group handling for {}", event.subject().id()); + DefaultGroupHandler dgh = DefaultGroupHandler.createGroupHandler( + event.subject().id(), + appId, + config, + linkService, + groupService); + dgh.createGroups(); + dghMap.put(event.subject().id(), dgh); + break; + case PORT_REMOVED: + if (dghMap.get(event.subject().id()) != null) { + dghMap.get(event.subject().id()).portDown(event.port().number()); + } + break; + default: + break; + } + + } + } + + private class InternalLinkListener implements LinkListener { + + @Override + public void event(LinkEvent event) { + switch (event.type()) { + case LINK_ADDED: + if (dghMap.get(event.subject().src().deviceId()) != null) { + dghMap.get(event.subject().src().deviceId()).linkUp(event.subject()); + } + break; + default: + break; + } + } + + } +} \ No newline at end of file diff --git a/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultTransitGroupHandler.java b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultTransitGroupHandler.java new file mode 100644 index 0000000000..97045397ca --- /dev/null +++ b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultTransitGroupHandler.java @@ -0,0 +1,177 @@ +/* + * 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.grouphandler; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.onosproject.core.ApplicationId; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.group.DefaultGroupBucket; +import org.onosproject.net.group.GroupBucket; +import org.onosproject.net.group.GroupBuckets; +import org.onosproject.net.group.GroupService; +import org.onosproject.net.link.LinkService; + +/** + * Default ECMP group handler creation module for a transit device. + * This component creates a set of ECMP groups for every neighbor + * that this device is connected to. + * For example, consider a network of 4 devices: D0 (Segment ID: 100), + * D1 (Segment ID: 101), D2 (Segment ID: 102) and D3 (Segment ID: 103), + * where D0 and D3 are edge devices and D1 and D2 are transit devices. + * Assume transit device D1 is connected to 2 neighbors (D0 and D3 ). + * The following groups will be created in D1: + * 1) all ports to D0 + with no label push, + * 2) all ports to D3 + with no label push, + */ +public class DefaultTransitGroupHandler extends DefaultGroupHandler { + + protected DefaultTransitGroupHandler(DeviceId deviceId, + ApplicationId appId, + DeviceProperties config, + LinkService linkService, + GroupService groupService) { + super(deviceId, appId, config, linkService, groupService); + } + + @Override + public void createGroups() { + Set neighbors = devicePortMap.keySet(); + if (neighbors == null || neighbors.isEmpty()) { + return; + } + + // Create all possible Neighbor sets from this router + // NOTE: Avoid any pairings of edge routers only + Set> sets = getPowerSetOfNeighbors(neighbors); + sets = filterEdgeRouterOnlyPairings(sets); + log.debug("createGroupsAtTransitRouter: The size of neighbor powerset " + + "for sw {} is {}", deviceId, sets.size()); + Set nsSet = new HashSet(); + for (Set combo : sets) { + if (combo.isEmpty()) { + continue; + } + NeighborSet ns = new NeighborSet(combo); + log.debug("createGroupsAtTransitRouter: sw {} combo {} ns {}", + deviceId, combo, ns); + nsSet.add(ns); + } + log.debug("createGroupsAtTransitRouter: The neighborset with label " + + "for sw {} is {}", deviceId, nsSet); + + createGroupsFromNeighborsets(nsSet); + } + + @Override + protected void newNeighbor(Link newNeighborLink) { + log.debug("New Neighbor: Updating groups for " + + "transit device {}", deviceId); + // Recompute neighbor power set + addNeighborAtPort(newNeighborLink.dst().deviceId(), + newNeighborLink.src().port()); + // Compute new neighbor sets due to the addition of new neighbor + Set nsSet = computeImpactedNeighborsetForPortEvent( + newNeighborLink.dst().deviceId(), + devicePortMap.keySet()); + createGroupsFromNeighborsets(nsSet); + } + + @Override + protected void newPortToExistingNeighbor(Link newNeighborLink) { + log.debug("New port to existing neighbor: Updating " + + "groups for transit device {}", deviceId); + addNeighborAtPort(newNeighborLink.dst().deviceId(), + newNeighborLink.src().port()); + Set nsSet = computeImpactedNeighborsetForPortEvent( + newNeighborLink.dst().deviceId(), + devicePortMap.keySet()); + for (NeighborSet ns : nsSet) { + // Create the new bucket to be updated + TrafficTreatment.Builder tBuilder = + DefaultTrafficTreatment.builder(); + tBuilder.setOutput(newNeighborLink.src().port()) + .setEthDst(deviceConfig.getDeviceMac( + newNeighborLink.dst().deviceId())) + .setEthSrc(nodeMacAddr) + .pushMpls() + .setMpls(ns.getEdgeLabel()); + GroupBucket updatedBucket = DefaultGroupBucket. + createSelectGroupBucket(tBuilder.build()); + GroupBuckets updatedBuckets = new GroupBuckets( + Arrays.asList(updatedBucket)); + log.debug("newPortToExistingNeighborAtEdgeRouter: " + + "groupService.addBucketsToGroup for neighborset{}", ns); + groupService.addBucketsToGroup(deviceId, ns, updatedBuckets, ns, appId); + } + } + + @Override + protected Set computeImpactedNeighborsetForPortEvent( + DeviceId impactedNeighbor, + Set updatedNeighbors) { + Set> powerSet = getPowerSetOfNeighbors(updatedNeighbors); + + Set tmp = updatedNeighbors; + tmp.remove(impactedNeighbor); + Set> tmpPowerSet = getPowerSetOfNeighbors(tmp); + + // Compute the impacted neighbor sets + powerSet.removeAll(tmpPowerSet); + + powerSet = filterEdgeRouterOnlyPairings(powerSet); + Set nsSet = new HashSet(); + for (Set combo : powerSet) { + if (combo.isEmpty()) { + continue; + } + NeighborSet ns = new NeighborSet(combo); + log.debug("createGroupsAtTransitRouter: sw {} combo {} ns {}", + deviceId, combo, ns); + nsSet.add(ns); + } + log.debug("computeImpactedNeighborsetForPortEvent: The neighborset with label " + + "for sw {} is {}", deviceId, nsSet); + + return nsSet; + } + + private Set> filterEdgeRouterOnlyPairings(Set> sets) { + Set> fiteredSets = new HashSet>(); + for (Set deviceSubSet : sets) { + if (deviceSubSet.size() > 1) { + boolean avoidEdgeRouterPairing = true; + for (DeviceId device : deviceSubSet) { + if (!deviceConfig.isEdgeDevice(device)) { + avoidEdgeRouterPairing = false; + break; + } + } + if (!avoidEdgeRouterPairing) { + fiteredSets.add(deviceSubSet); + } + } else { + fiteredSets.add(deviceSubSet); + } + } + return fiteredSets; + } +} diff --git a/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DeviceProperties.java b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DeviceProperties.java new file mode 100644 index 0000000000..685fa78227 --- /dev/null +++ b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DeviceProperties.java @@ -0,0 +1,57 @@ +/* + * 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.grouphandler; + +import java.util.List; + +import org.onlab.packet.MacAddress; +import org.onosproject.net.DeviceId; + +/** + * Mechanism through which group handler module retrieves + * the device specific attributes such as segment ID, + * Mac address...etc from group handler applications. + */ +public interface DeviceProperties { + /** + * Returns the segment id of a device to be used in group creation. + * + * @param deviceId device identifier + * @return segment id of a device + */ + int getSegmentId(DeviceId deviceId); + /** + * Returns the Mac address of a device to be used in group creation. + * + * @param deviceId device identifier + * @return mac address of a device + */ + MacAddress getDeviceMac(DeviceId deviceId); + /** + * Indicates whether a device is edge device or transit/core device. + * + * @param deviceId device identifier + * @return boolean + */ + boolean isEdgeDevice(DeviceId deviceId); + /** + * Returns all segment IDs to be considered in building auto + * + * created groups. + * @return list of segment IDs + */ + List getAllDeviceSegmentIds(); +} diff --git a/apps/grouphandler/src/main/java/org/onosproject/grouphandler/GroupBucketIdentifier.java b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/GroupBucketIdentifier.java new file mode 100644 index 0000000000..cf0ae5e9f3 --- /dev/null +++ b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/GroupBucketIdentifier.java @@ -0,0 +1,70 @@ +/* + * 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.grouphandler; + +import static com.google.common.base.Preconditions.checkNotNull; + +import org.onosproject.net.PortNumber; +import org.onosproject.net.group.GroupKey; + +/** + * Representation of policy group bucket identifier. Not exposed to + * the application and only to be used internally. + */ +public class GroupBucketIdentifier { + private int label; + private BucketOutputType type; + private PortNumber outPort; + private GroupKey outGroup; + + protected enum BucketOutputType { + PORT, + GROUP + } + + protected GroupBucketIdentifier(int label, + PortNumber outPort) { + this.label = label; + this.type = BucketOutputType.PORT; + this.outPort = checkNotNull(outPort); + this.outGroup = null; + } + + protected GroupBucketIdentifier(int label, + GroupKey outGroup) { + this.label = label; + this.type = BucketOutputType.GROUP; + this.outPort = null; + this.outGroup = checkNotNull(outGroup); + } + + protected int label() { + return this.label; + } + + protected BucketOutputType type() { + return this.type; + } + + protected PortNumber outPort() { + return this.outPort; + } + + protected GroupKey outGroup() { + return this.outGroup; + } +} + diff --git a/apps/grouphandler/src/main/java/org/onosproject/grouphandler/NeighborSet.java b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/NeighborSet.java new file mode 100644 index 0000000000..497b352473 --- /dev/null +++ b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/NeighborSet.java @@ -0,0 +1,123 @@ +/* + * 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.grouphandler; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import org.onosproject.net.DeviceId; +import org.onosproject.net.group.GroupKey; + +/** + * Representation of a set of neighbor switch dpids along with edge node + * label. Meant to be used as a lookup-key in a hash-map to retrieve an + * ECMP-group that hashes packets to a set of ports connecting to the + * neighbors in this set. + */ +public class NeighborSet implements GroupKey { + private final Set neighbors; + private final int edgeLabel; + + /** + * Constructor with set of neighbors. Edge label is + * default to -1. + * + * @param neighbors set of neighbors to be part of neighbor set + */ + public NeighborSet(Set neighbors) { + checkNotNull(neighbors); + this.edgeLabel = -1; + this.neighbors = new HashSet(); + this.neighbors.addAll(neighbors); + } + + /** + * Constructor with set of neighbors and edge label. + * + * @param neighbors set of neighbors to be part of neighbor set + * @param edgeLabel label to be pushed as part of group operation + */ + public NeighborSet(Set neighbors, int edgeLabel) { + checkNotNull(neighbors); + this.edgeLabel = edgeLabel; + this.neighbors = new HashSet(); + this.neighbors.addAll(neighbors); + } + + /** + * Default constructor for kryo serialization. + */ + public NeighborSet() { + this.edgeLabel = -1; + this.neighbors = new HashSet(); + } + + /** + * Gets the neighbors part of neighbor set. + * + * @return set of neighbor identifiers + */ + public Set getDeviceIds() { + return neighbors; + } + + /** + * Gets the label associated with neighbor set. + * + * @return integer + */ + public int getEdgeLabel() { + return edgeLabel; + } + + // The list of neighbor ids and label are used for comparison. + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof NeighborSet)) { + return false; + } + NeighborSet that = (NeighborSet) o; + return (this.neighbors.containsAll(that.neighbors) && + that.neighbors.containsAll(this.neighbors) && + (this.edgeLabel == that.edgeLabel)); + } + + // The list of neighbor ids and label are used for comparison. + @Override + public int hashCode() { + int result = 17; + int combinedHash = 0; + for (DeviceId d : neighbors) { + combinedHash = combinedHash + Objects.hash(d); + } + result = 31 * result + combinedHash + Objects.hash(edgeLabel); + + return result; + } + + @Override + public String toString() { + return " Neighborset Sw: " + neighbors + + " and Label: " + edgeLabel; + } +} diff --git a/apps/grouphandler/src/main/java/org/onosproject/grouphandler/PolicyGroupHandler.java b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/PolicyGroupHandler.java new file mode 100644 index 0000000000..4f57bd3365 --- /dev/null +++ b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/PolicyGroupHandler.java @@ -0,0 +1,345 @@ +/* + * 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.grouphandler; + +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +import org.onosproject.core.ApplicationId; +import org.onosproject.core.GroupId; +import org.onosproject.grouphandler.GroupBucketIdentifier.BucketOutputType; +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.DefaultGroupBucket; +import org.onosproject.net.group.DefaultGroupDescription; +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.GroupService; +import org.onosproject.net.link.LinkService; +import org.slf4j.Logger; + +/** + * A module to create group chains based on the specified device + * ports and label stack to be applied on each port. + */ +public class PolicyGroupHandler extends DefaultGroupHandler { + + private final Logger log = getLogger(getClass()); + private HashMap dependentGroups = + new HashMap(); + + /** + * Creates policy group handler object. + * + * @param deviceId device identifier + * @param appId application identifier + * @param config interface to retrieve the device properties + * @param linkService link service object + * @param groupService group service object + * @return policy group handler type + */ + public PolicyGroupHandler(DeviceId deviceId, + ApplicationId appId, + DeviceProperties config, + LinkService linkService, + GroupService groupService) { + super(deviceId, appId, config, linkService, groupService); + } + + public PolicyGroupIdentifier createPolicyGroupChain(String id, + List params) { + List bucketIds = new ArrayList(); + for (PolicyGroupParams param: params) { + List ports = param.getPorts(); + if (ports == null) { + log.warn("createPolicyGroupChain in sw {} with wrong " + + "input parameters", deviceId); + return null; + } + + int labelStackSize = (param.getLabelStack() != null) ? + param.getLabelStack().size() : 0; + + if (labelStackSize > 1) { + for (PortNumber sp : ports) { + PolicyGroupIdentifier previousGroupkey = null; + DeviceId neighbor = portDeviceMap.get(sp); + for (int idx = 0; idx < param.getLabelStack().size(); idx++) { + int label = param.getLabelStack().get(idx).intValue(); + if (idx == (labelStackSize - 1)) { + // Innermost Group + GroupBucketIdentifier bucketId = + new GroupBucketIdentifier(label, + previousGroupkey); + bucketIds.add(bucketId); + } else if (idx == 0) { + // Outermost Group + List outBuckets = new ArrayList(); + GroupBucketIdentifier bucketId = + new GroupBucketIdentifier(label, sp); + PolicyGroupIdentifier key = new + PolicyGroupIdentifier(id, + Arrays.asList(param), + Arrays.asList(bucketId)); + TrafficTreatment.Builder tBuilder = + DefaultTrafficTreatment.builder(); + tBuilder.setOutput(sp) + .setEthDst(deviceConfig. + getDeviceMac(neighbor)) + .setEthSrc(nodeMacAddr) + .pushMpls() + .setMpls(label); + outBuckets.add(DefaultGroupBucket. + createSelectGroupBucket(tBuilder.build())); + GroupDescription desc = new + DefaultGroupDescription(deviceId, + GroupDescription.Type.INDIRECT, + new GroupBuckets(outBuckets)); + //TODO: BoS + previousGroupkey = key; + groupService.addGroup(desc); + } else { + // Intermediate Groups + GroupBucketIdentifier bucketId = + new GroupBucketIdentifier(label, + previousGroupkey); + PolicyGroupIdentifier key = new + PolicyGroupIdentifier(id, + Arrays.asList(param), + Arrays.asList(bucketId)); + // Add to group dependency list + dependentGroups.put(previousGroupkey, key); + previousGroupkey = key; + } + } + } + } else { + int label = -1; + if (labelStackSize == 1) { + label = param.getLabelStack().get(0).intValue(); + } + for (PortNumber sp : ports) { + GroupBucketIdentifier bucketId = + new GroupBucketIdentifier(label, sp); + bucketIds.add(bucketId); + } + } + } + PolicyGroupIdentifier innermostGroupkey = null; + if (!bucketIds.isEmpty()) { + innermostGroupkey = new + PolicyGroupIdentifier(id, + params, + bucketIds); + // Add to group dependency list + boolean fullyResolved = true; + for (GroupBucketIdentifier bucketId:bucketIds) { + if (bucketId.type() == BucketOutputType.GROUP) { + dependentGroups.put(bucketId.outGroup(), + innermostGroupkey); + fullyResolved = false; + } + } + + if (fullyResolved) { + List outBuckets = new ArrayList(); + for (GroupBucketIdentifier bucketId:bucketIds) { + DeviceId neighbor = portDeviceMap. + get(bucketId.outPort()); + TrafficTreatment.Builder tBuilder = + DefaultTrafficTreatment.builder(); + tBuilder.setOutput(bucketId.outPort()) + .setEthDst(deviceConfig. + getDeviceMac(neighbor)) + .setEthSrc(nodeMacAddr) + .pushMpls() + .setMpls(bucketId.label()); + //TODO: BoS + outBuckets.add(DefaultGroupBucket. + createSelectGroupBucket(tBuilder.build())); + } + GroupDescription desc = new + DefaultGroupDescription(deviceId, + GroupDescription.Type.SELECT, + new GroupBuckets(outBuckets)); + groupService.addGroup(desc); + } + } + return innermostGroupkey; + } + + @Override + protected void handleGroupEvent(GroupEvent event) { + if (event.type() == GroupEvent.Type.GROUP_ADDED) { + if (dependentGroups.get(event.subject().appCookie()) != null) { + PolicyGroupIdentifier dependentGroupKey = (PolicyGroupIdentifier) + dependentGroups.get(event.subject().appCookie()); + dependentGroups.remove(event.subject().appCookie()); + boolean fullyResolved = true; + for (GroupBucketIdentifier bucketId: + dependentGroupKey.bucketIds()) { + if (bucketId.type() != BucketOutputType.GROUP) { + continue; + } + if (dependentGroups.containsKey(bucketId.outGroup())) { + fullyResolved = false; + break; + } + } + + if (fullyResolved) { + List outBuckets = new ArrayList(); + for (GroupBucketIdentifier bucketId: + dependentGroupKey.bucketIds()) { + TrafficTreatment.Builder tBuilder = + DefaultTrafficTreatment.builder(); + tBuilder.pushMpls() + .setMpls(bucketId.label()); + //TODO: BoS + if (bucketId.type() == BucketOutputType.PORT) { + DeviceId neighbor = portDeviceMap. + get(bucketId.outPort()); + tBuilder.setOutput(bucketId.outPort()) + .setEthDst(deviceConfig. + getDeviceMac(neighbor)) + .setEthSrc(nodeMacAddr); + } else { + if (groupService. + getGroup(deviceId, + bucketId.outGroup()) == null) { + throw new IllegalStateException(); + } + GroupId indirectGroupId = groupService. + getGroup(deviceId, + bucketId.outGroup()).id(); + tBuilder.group(indirectGroupId); + } + outBuckets.add(DefaultGroupBucket. + createSelectGroupBucket(tBuilder.build())); + } + GroupDescription desc = new + DefaultGroupDescription(deviceId, + GroupDescription.Type.SELECT, + new GroupBuckets(outBuckets)); + groupService.addGroup(desc); + } + } + } + } + + public GroupKey generatePolicyGroupKey(String id, + List params) { + List bucketIds = new ArrayList(); + for (PolicyGroupParams param: params) { + List ports = param.getPorts(); + if (ports == null) { + log.warn("generateGroupKey in sw {} with wrong " + + "input parameters", deviceId); + return null; + } + + int labelStackSize = (param.getLabelStack() != null) + ? param.getLabelStack().size() : 0; + + if (labelStackSize > 1) { + for (PortNumber sp : ports) { + PolicyGroupIdentifier previousGroupkey = null; + for (int idx = 0; idx < param.getLabelStack().size(); idx++) { + int label = param.getLabelStack().get(idx).intValue(); + if (idx == (labelStackSize - 1)) { + // Innermost Group + GroupBucketIdentifier bucketId = + new GroupBucketIdentifier(label, + previousGroupkey); + bucketIds.add(bucketId); + } else if (idx == 0) { + // Outermost Group + GroupBucketIdentifier bucketId = + new GroupBucketIdentifier(label, sp); + PolicyGroupIdentifier key = new + PolicyGroupIdentifier(id, + Arrays.asList(param), + Arrays.asList(bucketId)); + previousGroupkey = key; + } else { + // Intermediate Groups + GroupBucketIdentifier bucketId = + new GroupBucketIdentifier(label, + previousGroupkey); + PolicyGroupIdentifier key = new + PolicyGroupIdentifier(id, + Arrays.asList(param), + Arrays.asList(bucketId)); + previousGroupkey = key; + } + } + } + } else { + int label = -1; + if (labelStackSize == 1) { + label = param.getLabelStack().get(0).intValue(); + } + for (PortNumber sp : ports) { + GroupBucketIdentifier bucketId = + new GroupBucketIdentifier(label, sp); + bucketIds.add(bucketId); + } + } + } + PolicyGroupIdentifier innermostGroupkey = null; + if (!bucketIds.isEmpty()) { + innermostGroupkey = new + PolicyGroupIdentifier(id, + params, + bucketIds); + } + return innermostGroupkey; + } + + public void removeGroupChain(GroupKey key) { + if (!(key instanceof PolicyGroupIdentifier)) { + throw new IllegalArgumentException(); + } + List groupsToBeDeleted = new ArrayList(); + groupsToBeDeleted.add(key); + + Iterator it = groupsToBeDeleted.iterator(); + + while (it.hasNext()) { + PolicyGroupIdentifier innerMostGroupKey = + (PolicyGroupIdentifier) it.next(); + for (GroupBucketIdentifier bucketId: + innerMostGroupKey.bucketIds()) { + if (bucketId.type() != BucketOutputType.GROUP) { + groupsToBeDeleted.add(bucketId.outGroup()); + } + } + groupService.removeGroup(deviceId, innerMostGroupKey, appId); + it.remove(); + } + } + +} \ No newline at end of file diff --git a/apps/grouphandler/src/main/java/org/onosproject/grouphandler/PolicyGroupIdentifier.java b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/PolicyGroupIdentifier.java new file mode 100644 index 0000000000..d213e0cf08 --- /dev/null +++ b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/PolicyGroupIdentifier.java @@ -0,0 +1,92 @@ +/* + * 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.grouphandler; + +import java.util.List; + +import org.onosproject.net.group.GroupKey; + +/** + * Representation of policy based group identifiers. + * Opaque to group handler applications and only the outermost + * policy group identifier in a chain is visible to the applications. + */ +public class PolicyGroupIdentifier implements GroupKey { + private String id; + private List inputParams; + private List bucketIds; + + /** + * Constructor. + * + * @param id unique identifier associated with the policy group + * @param input policy group params associated with this group + * @param bucketIds buckets associated with this group + */ + protected PolicyGroupIdentifier(String id, + List input, + List bucketIds) { + this.id = id; + this.inputParams = input; + this.bucketIds = bucketIds; + } + + /** + * Returns the bucket identifier list associated with the policy + * group identifier. + * + * @return list of bucket identifier + */ + protected List bucketIds() { + return this.bucketIds; + } + + @Override + public int hashCode() { + int result = 17; + int combinedHash = 0; + for (PolicyGroupParams input:inputParams) { + combinedHash = combinedHash + input.hashCode(); + } + for (GroupBucketIdentifier bucketId:bucketIds) { + combinedHash = combinedHash + bucketId.hashCode(); + } + result = 31 * result + combinedHash; + + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof PolicyGroupIdentifier) { + PolicyGroupIdentifier that = (PolicyGroupIdentifier) obj; + boolean result = this.id.equals(that.id); + result = result && + this.inputParams.containsAll(that.inputParams) && + that.inputParams.containsAll(this.inputParams); + result = result && + this.bucketIds.containsAll(that.bucketIds) && + that.bucketIds.containsAll(this.bucketIds); + return result; + } + + return false; + } +} diff --git a/apps/grouphandler/src/main/java/org/onosproject/grouphandler/PolicyGroupParams.java b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/PolicyGroupParams.java new file mode 100644 index 0000000000..d2fcffa92d --- /dev/null +++ b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/PolicyGroupParams.java @@ -0,0 +1,92 @@ +/* + * 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.grouphandler; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.List; +import java.util.Objects; + +import org.onosproject.net.PortNumber; + +/** + * Representation of parameters used to create policy based groups. + */ +public class PolicyGroupParams { + private final List ports; + private final List labelStack; + + /** + * Constructor. + * + * @param labelStack mpls label stack to be applied on the ports + * @param ports ports to be part of the policy group + */ + public PolicyGroupParams(List labelStack, + List ports) { + this.ports = checkNotNull(ports); + this.labelStack = checkNotNull(labelStack); + } + + /** + * Returns the ports associated with the policy group params. + * + * @return list of port numbers + */ + public List getPorts() { + return ports; + } + + /** + * Returns the label stack associated with the policy group params. + * + * @return list of integers + */ + public List getLabelStack() { + return labelStack; + } + + @Override + public int hashCode() { + int result = 17; + int combinedHash = 0; + for (PortNumber port:ports) { + combinedHash = combinedHash + port.hashCode(); + } + combinedHash = combinedHash + Objects.hash(labelStack); + result = 31 * result + combinedHash; + + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof PolicyGroupParams) { + PolicyGroupParams that = (PolicyGroupParams) obj; + boolean result = this.labelStack.equals(that.labelStack); + result = result && + this.ports.containsAll(that.ports) && + that.ports.containsAll(this.ports); + return result; + } + + return false; + } +} diff --git a/core/api/src/main/java/org/onosproject/net/group/DefaultGroup.java b/core/api/src/main/java/org/onosproject/net/group/DefaultGroup.java index eba2c6c987..92486254cc 100644 --- a/core/api/src/main/java/org/onosproject/net/group/DefaultGroup.java +++ b/core/api/src/main/java/org/onosproject/net/group/DefaultGroup.java @@ -15,6 +15,7 @@ */ package org.onosproject.net.group; +import static com.google.common.base.MoreObjects.toStringHelper; import static org.slf4j.LoggerFactory.getLogger; import java.util.Objects; @@ -205,4 +206,13 @@ public class DefaultGroup extends DefaultGroupDescription } return false; } + + @Override + public String toString() { + return toStringHelper(this) + .add("description", super.toString()) + .add("groupid", id) + .add("state", state) + .toString(); + } } diff --git a/core/api/src/main/java/org/onosproject/net/group/DefaultGroupBucket.java b/core/api/src/main/java/org/onosproject/net/group/DefaultGroupBucket.java index 931cc71cfc..ff1271ed98 100644 --- a/core/api/src/main/java/org/onosproject/net/group/DefaultGroupBucket.java +++ b/core/api/src/main/java/org/onosproject/net/group/DefaultGroupBucket.java @@ -15,6 +15,7 @@ */ package org.onosproject.net.group; +import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -212,4 +213,11 @@ public final class DefaultGroupBucket implements GroupBucket { return false; } + @Override + public String toString() { + return toStringHelper(this) + .add("type", type) + .add("treatment", treatment) + .toString(); + } } diff --git a/core/api/src/main/java/org/onosproject/net/group/DefaultGroupDescription.java b/core/api/src/main/java/org/onosproject/net/group/DefaultGroupDescription.java index 8d374c1b98..385a5d6a49 100644 --- a/core/api/src/main/java/org/onosproject/net/group/DefaultGroupDescription.java +++ b/core/api/src/main/java/org/onosproject/net/group/DefaultGroupDescription.java @@ -15,6 +15,7 @@ */ package org.onosproject.net.group; +import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkNotNull; import java.util.Objects; @@ -168,4 +169,13 @@ public class DefaultGroupDescription implements GroupDescription { return false; } + @Override + public String toString() { + return toStringHelper(this) + .add("deviceId", deviceId) + .add("type", type) + .add("buckets", buckets) + .add("appId", appId) + .toString(); + } } \ No newline at end of file diff --git a/core/api/src/main/java/org/onosproject/net/group/GroupBuckets.java b/core/api/src/main/java/org/onosproject/net/group/GroupBuckets.java index 5ca8f3084e..26cd5f6cb5 100644 --- a/core/api/src/main/java/org/onosproject/net/group/GroupBuckets.java +++ b/core/api/src/main/java/org/onosproject/net/group/GroupBuckets.java @@ -15,6 +15,7 @@ */ package org.onosproject.net.group; +import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkNotNull; import java.util.List; @@ -66,4 +67,10 @@ public final class GroupBuckets { return false; } + @Override + public String toString() { + return toStringHelper(this) + .add("buckets", buckets) + .toString(); + } } \ No newline at end of file diff --git a/core/api/src/main/java/org/onosproject/net/group/GroupStore.java b/core/api/src/main/java/org/onosproject/net/group/GroupStore.java index 6803b58ff0..e7bf4f80d3 100644 --- a/core/api/src/main/java/org/onosproject/net/group/GroupStore.java +++ b/core/api/src/main/java/org/onosproject/net/group/GroupStore.java @@ -132,8 +132,9 @@ public interface GroupStore extends Store { * Indicates the first group audit is completed. * * @param deviceId the device ID + * @param completed initial audit status */ - void deviceInitialAuditCompleted(DeviceId deviceId); + void deviceInitialAuditCompleted(DeviceId deviceId, boolean completed); /** * Retrieves the initial group audit status for a device. diff --git a/core/net/src/main/java/org/onosproject/net/group/impl/GroupManager.java b/core/net/src/main/java/org/onosproject/net/group/impl/GroupManager.java index 4cb21e5900..1490b0b417 100644 --- a/core/net/src/main/java/org/onosproject/net/group/impl/GroupManager.java +++ b/core/net/src/main/java/org/onosproject/net/group/impl/GroupManager.java @@ -32,6 +32,9 @@ import org.onosproject.core.ApplicationId; import org.onosproject.event.AbstractListenerRegistry; import org.onosproject.event.EventDeliveryService; import org.onosproject.net.DeviceId; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceService; import org.onosproject.net.group.Group; import org.onosproject.net.group.GroupBuckets; import org.onosproject.net.group.GroupDescription; @@ -67,10 +70,14 @@ public class GroupManager private final AbstractListenerRegistry listenerRegistry = new AbstractListenerRegistry<>(); private final GroupStoreDelegate delegate = new InternalGroupStoreDelegate(); + private final DeviceListener deviceListener = new InternalDeviceListener(); @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected GroupStore store; + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected EventDeliveryService eventDispatcher; @@ -78,6 +85,7 @@ public class GroupManager public void activate() { store.setDelegate(delegate); eventDispatcher.addSink(GroupEvent.class, listenerRegistry); + deviceService.addListener(deviceListener); log.info("Started"); } @@ -232,6 +240,8 @@ public class GroupManager GroupOperations groupOps = null; switch (event.type()) { case GROUP_ADD_REQUESTED: + log.debug("GROUP_ADD_REQUESTED for Group {} on device {}", + group.id(), group.deviceId()); GroupOperation groupAddOp = GroupOperation. createAddGroupOperation(group.id(), group.type(), @@ -242,6 +252,8 @@ public class GroupManager break; case GROUP_UPDATE_REQUESTED: + log.debug("GROUP_UPDATE_REQUESTED for Group {} on device {}", + group.id(), group.deviceId()); GroupOperation groupModifyOp = GroupOperation. createModifyGroupOperation(group.id(), group.type(), @@ -252,6 +264,8 @@ public class GroupManager break; case GROUP_REMOVE_REQUESTED: + log.debug("GROUP_REMOVE_REQUESTED for Group {} on device {}", + group.id(), group.deviceId()); GroupOperation groupDeleteOp = GroupOperation. createDeleteGroupOperation(group.id(), group.type()); @@ -294,10 +308,14 @@ public class GroupManager GroupProvider gp = getProvider(group.deviceId()); switch (group.state()) { case PENDING_DELETE: + log.debug("Group {} delete confirmation from device {}", + group, group.deviceId()); store.removeGroupEntry(group); break; case ADDED: case PENDING_ADD: + log.debug("Group {} is in store but not on device {}", + group, group.deviceId()); GroupOperation groupAddOp = GroupOperation. createAddGroupOperation(group.id(), group.type(), @@ -314,7 +332,8 @@ public class GroupManager private void extraneousGroup(Group group) { - log.debug("Group {} is on switch but not in store.", group); + log.debug("Group {} is on device {} but not in store.", + group, group.deviceId()); checkValidity(); store.addOrUpdateExtraneousGroupEntry(group); } @@ -322,13 +341,16 @@ public class GroupManager private void groupAdded(Group group) { checkValidity(); - log.trace("Group {}", group); + log.trace("Group {} Added or Updated in device {}", + group, group.deviceId()); store.addOrUpdateGroupEntry(group); } @Override public void pushGroupMetrics(DeviceId deviceId, Collection groupEntries) { + log.trace("Received group metrics from device {}", + deviceId); boolean deviceInitialAuditStatus = store.deviceInitialAuditStatus(deviceId); Set southboundGroupEntries = @@ -338,31 +360,75 @@ public class GroupManager Set extraneousStoredEntries = Sets.newHashSet(store.getExtraneousGroups(deviceId)); + log.trace("Displaying all southboundGroupEntries for device {}", deviceId); + for (Iterator it = southboundGroupEntries.iterator(); it.hasNext();) { + Group group = it.next(); + log.trace("Group {} in device {}", group, deviceId); + } + + log.trace("Displaying all stored group entries for device {}", deviceId); + for (Iterator it = storedGroupEntries.iterator(); it.hasNext();) { + Group group = it.next(); + log.trace("Stored Group {} for device {}", group, deviceId); + } + for (Iterator it = southboundGroupEntries.iterator(); it.hasNext();) { Group group = it.next(); if (storedGroupEntries.remove(group)) { // we both have the group, let's update some info then. + log.trace("Group AUDIT: group {} exists " + + "in both planes for device {}", + group.id(), deviceId); groupAdded(group); it.remove(); } } for (Group group : southboundGroupEntries) { // there are groups in the switch that aren't in the store + log.trace("Group AUDIT: extraneous group {} exists " + + "in data plane for device {}", + group.id(), deviceId); extraneousStoredEntries.remove(group); extraneousGroup(group); } for (Group group : storedGroupEntries) { // there are groups in the store that aren't in the switch + log.trace("Group AUDIT: group {} missing " + + "in data plane for device {}", + group.id(), deviceId); groupMissing(group); } for (Group group : extraneousStoredEntries) { // there are groups in the extraneous store that // aren't in the switch + log.trace("Group AUDIT: clearing extransoeus group {} " + + "from store for device {}", + group.id(), deviceId); store.removeExtraneousGroupEntry(group); } if (!deviceInitialAuditStatus) { - store.deviceInitialAuditCompleted(deviceId); + log.debug("Group AUDIT: Setting device {} initial " + + "AUDIT completed", deviceId); + store.deviceInitialAuditCompleted(deviceId, true); + } + } + } + + private class InternalDeviceListener implements DeviceListener { + + @Override + public void event(DeviceEvent event) { + switch (event.type()) { + case DEVICE_REMOVED: + log.debug("Clearing device {} initial " + + "AUDIT completed status as device is going down", + event.subject().id()); + store.deviceInitialAuditCompleted(event.subject().id(), false); + break; + + default: + break; } } } diff --git a/core/net/src/test/java/org/onosproject/net/group/impl/GroupManagerTest.java b/core/net/src/test/java/org/onosproject/net/group/impl/GroupManagerTest.java index 7099464a99..2fe5e4b886 100644 --- a/core/net/src/test/java/org/onosproject/net/group/impl/GroupManagerTest.java +++ b/core/net/src/test/java/org/onosproject/net/group/impl/GroupManagerTest.java @@ -32,6 +32,7 @@ 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.device.impl.DeviceManager; import org.onosproject.net.flow.DefaultTrafficTreatment; import org.onosproject.net.flow.TrafficTreatment; import org.onosproject.net.group.DefaultGroup; @@ -81,6 +82,7 @@ public class GroupManagerTest { public void setUp() { mgr = new GroupManager(); groupService = mgr; + mgr.deviceService = new DeviceManager(); mgr.store = new SimpleGroupStore(); mgr.eventDispatcher = new TestEventDispatcher(); providerRegistry = mgr; @@ -147,11 +149,34 @@ public class GroupManagerTest { */ @Test public void testGroupService() { + // Test Group creation before AUDIT process + testGroupCreationBeforeAudit(); + + // Test initial group audit process + testInitialAuditWithPendingGroupRequests(); + + // Test audit with extraneous and missing groups + testAuditWithExtraneousMissingGroups(); + + // Test audit with confirmed groups + testAuditWithConfirmedGroups(); + + // Test group add bucket operations + testAddBuckets(); + + // Test group remove bucket operations + testRemoveBuckets(); + + // Test group remove operations + testRemoveGroup(); + } + + // Test Group creation before AUDIT process + private void testGroupCreationBeforeAudit() { PortNumber[] ports1 = {PortNumber.portNumber(31), PortNumber.portNumber(32)}; PortNumber[] ports2 = {PortNumber.portNumber(41), PortNumber.portNumber(42)}; - // Test Group creation before AUDIT process TestGroupKey key = new TestGroupKey("group1BeforeAudit"); List buckets = new ArrayList(); List outPorts = new ArrayList(); @@ -177,8 +202,14 @@ public class GroupManagerTest { internalProvider.validate(DID, null); assertEquals(null, groupService.getGroup(DID, key)); assertEquals(0, Iterables.size(groupService.getGroups(DID, appId))); + } - // Test initial group audit process + // Test initial AUDIT process with pending group requests + private void testInitialAuditWithPendingGroupRequests() { + PortNumber[] ports1 = {PortNumber.portNumber(31), + PortNumber.portNumber(32)}; + PortNumber[] ports2 = {PortNumber.portNumber(41), + PortNumber.portNumber(42)}; GroupId gId1 = new DefaultGroupId(1); Group group1 = createSouthboundGroupEntry(gId1, Arrays.asList(ports1), @@ -193,50 +224,76 @@ public class GroupManagerTest { providerService.pushGroupMetrics(DID, groupEntries); // First group metrics would trigger the device audit completion // post which all pending group requests are also executed. + TestGroupKey key = new TestGroupKey("group1BeforeAudit"); Group createdGroup = groupService.getGroup(DID, key); int createdGroupId = createdGroup.id().id(); assertNotEquals(gId1.id(), createdGroupId); assertNotEquals(gId2.id(), createdGroupId); + List expectedGroupOps = Arrays.asList( GroupOperation.createDeleteGroupOperation(gId1, Group.Type.SELECT), GroupOperation.createAddGroupOperation( createdGroup.id(), Group.Type.SELECT, - groupBuckets)); + createdGroup.buckets())); internalProvider.validate(DID, expectedGroupOps); + } - group1 = createSouthboundGroupEntry(gId1, + // Test AUDIT process with extraneous groups and missing groups + private void testAuditWithExtraneousMissingGroups() { + PortNumber[] ports1 = {PortNumber.portNumber(31), + PortNumber.portNumber(32)}; + PortNumber[] ports2 = {PortNumber.portNumber(41), + PortNumber.portNumber(42)}; + GroupId gId1 = new DefaultGroupId(1); + Group group1 = createSouthboundGroupEntry(gId1, Arrays.asList(ports1), 0); - group2 = createSouthboundGroupEntry(gId2, + GroupId gId2 = new DefaultGroupId(2); + Group group2 = createSouthboundGroupEntry(gId2, Arrays.asList(ports2), 0); - groupEntries = Arrays.asList(group1, group2); + List groupEntries = Arrays.asList(group1, group2); providerService.pushGroupMetrics(DID, groupEntries); - expectedGroupOps = Arrays.asList( + TestGroupKey key = new TestGroupKey("group1BeforeAudit"); + Group createdGroup = groupService.getGroup(DID, key); + List expectedGroupOps = Arrays.asList( GroupOperation.createDeleteGroupOperation(gId1, Group.Type.SELECT), GroupOperation.createDeleteGroupOperation(gId2, Group.Type.SELECT), GroupOperation.createAddGroupOperation(createdGroup.id(), Group.Type.SELECT, - groupBuckets)); + createdGroup.buckets())); internalProvider.validate(DID, expectedGroupOps); + } + // Test AUDIT with confirmed groups + private void testAuditWithConfirmedGroups() { + TestGroupKey key = new TestGroupKey("group1BeforeAudit"); + Group createdGroup = groupService.getGroup(DID, key); createdGroup = new DefaultGroup(createdGroup.id(), DID, Group.Type.SELECT, - groupBuckets); - groupEntries = Arrays.asList(createdGroup); + createdGroup.buckets()); + List groupEntries = Arrays.asList(createdGroup); providerService.pushGroupMetrics(DID, groupEntries); internalListener.validateEvent(Arrays.asList(GroupEvent.Type.GROUP_ADDED)); + } - // Test group add bucket operations + // Test group add bucket operations + private void testAddBuckets() { TestGroupKey addKey = new TestGroupKey("group1AddBuckets"); + + TestGroupKey prevKey = new TestGroupKey("group1BeforeAudit"); + Group createdGroup = groupService.getGroup(DID, prevKey); + List buckets = new ArrayList(); + buckets.addAll(createdGroup.buckets().buckets()); + PortNumber[] addPorts = {PortNumber.portNumber(51), PortNumber.portNumber(52)}; - outPorts.clear(); + List outPorts = new ArrayList(); outPorts.addAll(Arrays.asList(addPorts)); List addBuckets = new ArrayList(); for (PortNumber portNumber: outPorts) { @@ -253,26 +310,34 @@ public class GroupManagerTest { } GroupBuckets groupAddBuckets = new GroupBuckets(addBuckets); groupService.addBucketsToGroup(DID, - key, + prevKey, groupAddBuckets, addKey, appId); GroupBuckets updatedBuckets = new GroupBuckets(buckets); - expectedGroupOps = Arrays.asList( + List 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); + List groupEntries = Arrays.asList(existingGroup); providerService.pushGroupMetrics(DID, groupEntries); internalListener.validateEvent(Arrays.asList(GroupEvent.Type.GROUP_UPDATED)); + } - // Test group remove bucket operations + // Test group remove bucket operations + private void testRemoveBuckets() { TestGroupKey removeKey = new TestGroupKey("group1RemoveBuckets"); + + TestGroupKey prevKey = new TestGroupKey("group1AddBuckets"); + Group createdGroup = groupService.getGroup(DID, prevKey); + List buckets = new ArrayList(); + buckets.addAll(createdGroup.buckets().buckets()); + PortNumber[] removePorts = {PortNumber.portNumber(31), PortNumber.portNumber(32)}; - outPorts.clear(); + List outPorts = new ArrayList(); outPorts.addAll(Arrays.asList(removePorts)); List removeBuckets = new ArrayList(); for (PortNumber portNumber: outPorts) { @@ -289,28 +354,32 @@ public class GroupManagerTest { } GroupBuckets groupRemoveBuckets = new GroupBuckets(removeBuckets); groupService.removeBucketsFromGroup(DID, - addKey, + prevKey, groupRemoveBuckets, removeKey, appId); - updatedBuckets = new GroupBuckets(buckets); - expectedGroupOps = Arrays.asList( + GroupBuckets updatedBuckets = new GroupBuckets(buckets); + List expectedGroupOps = Arrays.asList( GroupOperation.createModifyGroupOperation(createdGroup.id(), Group.Type.SELECT, updatedBuckets)); internalProvider.validate(DID, expectedGroupOps); - existingGroup = groupService.getGroup(DID, removeKey); - groupEntries = Arrays.asList(existingGroup); + Group existingGroup = groupService.getGroup(DID, removeKey); + List 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(), + // Test group remove operations + private void testRemoveGroup() { + TestGroupKey currKey = new TestGroupKey("group1RemoveBuckets"); + Group existingGroup = groupService.getGroup(DID, currKey); + groupService.removeGroup(DID, currKey, appId); + List expectedGroupOps = Arrays.asList( + GroupOperation.createDeleteGroupOperation(existingGroup.id(), Group.Type.SELECT)); internalProvider.validate(DID, expectedGroupOps); - groupEntries = Collections.emptyList(); + List groupEntries = Collections.emptyList(); providerService.pushGroupMetrics(DID, groupEntries); internalListener.validateEvent(Arrays.asList(GroupEvent.Type.GROUP_REMOVED)); } diff --git a/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleGroupStore.java b/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleGroupStore.java index 9258dcc63f..e2adf8cc28 100644 --- a/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleGroupStore.java +++ b/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleGroupStore.java @@ -261,6 +261,11 @@ public class SimpleGroupStore } private void storeGroupDescriptionInternal(GroupDescription groupDesc) { + // 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(getFreeGroupIdValue(groupDesc.deviceId())); // Create a group entry object @@ -448,29 +453,41 @@ public class SimpleGroupStore } @Override - public void deviceInitialAuditCompleted(DeviceId deviceId) { + public void deviceInitialAuditCompleted(DeviceId deviceId, + boolean completed) { synchronized (deviceAuditStatus) { - deviceAuditStatus.putIfAbsent(deviceId, true); - // Execute all pending group requests - ConcurrentMap pendingGroupRequests = - getPendingGroupKeyTable(deviceId); - for (Group group:pendingGroupRequests.values()) { - GroupDescription tmp = new DefaultGroupDescription( - group.deviceId(), - group.type(), - group.buckets(), - group.appCookie(), - group.appId()); - storeGroupDescriptionInternal(tmp); + if (completed) { + log.debug("deviceInitialAuditCompleted: AUDIT " + + "completed for device {}", deviceId); + deviceAuditStatus.put(deviceId, true); + // Execute all pending group requests + ConcurrentMap pendingGroupRequests = + getPendingGroupKeyTable(deviceId); + for (Group group:pendingGroupRequests.values()) { + GroupDescription tmp = new DefaultGroupDescription( + group.deviceId(), + group.type(), + group.buckets(), + group.appCookie(), + group.appId()); + storeGroupDescriptionInternal(tmp); + } + getPendingGroupKeyTable(deviceId).clear(); + } else { + if (deviceAuditStatus.get(deviceId)) { + log.debug("deviceInitialAuditCompleted: Clearing AUDIT " + + "status for device {}", deviceId); + deviceAuditStatus.put(deviceId, false); + } } - getPendingGroupKeyTable(deviceId).clear(); } } @Override public boolean deviceInitialAuditStatus(DeviceId deviceId) { synchronized (deviceAuditStatus) { - return (deviceAuditStatus.get(deviceId) != null) ? true : false; + return (deviceAuditStatus.get(deviceId) != null) + ? deviceAuditStatus.get(deviceId) : false; } } diff --git a/core/store/trivial/src/test/java/org/onosproject/store/trivial/impl/SimpleGroupStoreTest.java b/core/store/trivial/src/test/java/org/onosproject/store/trivial/impl/SimpleGroupStoreTest.java index f890754130..712adcbf35 100644 --- a/core/store/trivial/src/test/java/org/onosproject/store/trivial/impl/SimpleGroupStoreTest.java +++ b/core/store/trivial/src/test/java/org/onosproject/store/trivial/impl/SimpleGroupStoreTest.java @@ -19,6 +19,7 @@ import static org.junit.Assert.assertEquals; import static org.onosproject.net.DeviceId.deviceId; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.junit.After; @@ -53,6 +54,8 @@ import com.google.common.collect.Iterables; public class SimpleGroupStoreTest { private SimpleGroupStore simpleGroupStore; + private final ApplicationId appId = + new DefaultApplicationId(2, "org.groupstore.test"); public static final DeviceId D1 = deviceId("of:1"); @@ -167,16 +170,42 @@ public class SimpleGroupStoreTest { @Test public void testGroupStoreOperations() { // Set the Device AUDIT completed in the store - simpleGroupStore.deviceInitialAuditCompleted(D1); + simpleGroupStore.deviceInitialAuditCompleted(D1, true); - ApplicationId appId = - new DefaultApplicationId(2, "org.groupstore.test"); - TestGroupKey key = new TestGroupKey("group1"); + // Testing storeGroup operation + TestGroupKey newKey = new TestGroupKey("group1"); + testStoreAndGetGroup(newKey); + + // Testing addOrUpdateGroupEntry operation from southbound + TestGroupKey currKey = newKey; + testAddGroupEntryFromSB(currKey); + + // Testing updateGroupDescription for ADD operation from northbound + newKey = new TestGroupKey("group1AddBuckets"); + testAddBuckets(currKey, newKey); + + // Testing updateGroupDescription for REMOVE operation from northbound + currKey = newKey; + newKey = new TestGroupKey("group1RemoveBuckets"); + testRemoveBuckets(currKey, newKey); + + // Testing addOrUpdateGroupEntry operation from southbound + currKey = newKey; + testUpdateGroupEntryFromSB(currKey); + + // Testing deleteGroupDescription operation from northbound + testDeleteGroup(currKey); + + // Testing removeGroupEntry operation from southbound + testRemoveGroupFromSB(currKey); + } + + // Testing storeGroup operation + private void testStoreAndGetGroup(TestGroupKey key) { PortNumber[] ports = {PortNumber.portNumber(31), PortNumber.portNumber(32)}; List outPorts = new ArrayList(); - outPorts.add(ports[0]); - outPorts.add(ports[1]); + outPorts.addAll(Arrays.asList(ports)); List buckets = new ArrayList(); for (PortNumber portNumber: outPorts) { @@ -220,23 +249,44 @@ public class SimpleGroupStoreTest { } assertEquals(1, groupCount); simpleGroupStore.unsetDelegate(checkStoreGroupDelegate); + } + + // Testing addOrUpdateGroupEntry operation from southbound + private void testAddGroupEntryFromSB(TestGroupKey currKey) { + Group existingGroup = simpleGroupStore.getGroup(D1, currKey); - // Testing addOrUpdateGroupEntry operation from southbound InternalGroupStoreDelegate addGroupEntryDelegate = - new InternalGroupStoreDelegate(key, - groupBuckets, + new InternalGroupStoreDelegate(currKey, + existingGroup.buckets(), GroupEvent.Type.GROUP_ADDED); simpleGroupStore.setDelegate(addGroupEntryDelegate); - simpleGroupStore.addOrUpdateGroupEntry(createdGroup); + simpleGroupStore.addOrUpdateGroupEntry(existingGroup); simpleGroupStore.unsetDelegate(addGroupEntryDelegate); + } + + // Testing addOrUpdateGroupEntry operation from southbound + private void testUpdateGroupEntryFromSB(TestGroupKey currKey) { + Group existingGroup = simpleGroupStore.getGroup(D1, currKey); + + InternalGroupStoreDelegate updateGroupEntryDelegate = + new InternalGroupStoreDelegate(currKey, + existingGroup.buckets(), + GroupEvent.Type.GROUP_UPDATED); + simpleGroupStore.setDelegate(updateGroupEntryDelegate); + simpleGroupStore.addOrUpdateGroupEntry(existingGroup); + simpleGroupStore.unsetDelegate(updateGroupEntryDelegate); + } + + // Testing updateGroupDescription for ADD operation from northbound + private void testAddBuckets(TestGroupKey currKey, TestGroupKey addKey) { + Group existingGroup = simpleGroupStore.getGroup(D1, currKey); + List buckets = new ArrayList(); + buckets.addAll(existingGroup.buckets().buckets()); - // Testing updateGroupDescription for ADD operation from northbound - TestGroupKey addKey = new TestGroupKey("group1AddBuckets"); PortNumber[] newNeighborPorts = {PortNumber.portNumber(41), PortNumber.portNumber(42)}; List newOutPorts = new ArrayList(); - newOutPorts.add(newNeighborPorts[0]); - newOutPorts.add(newNeighborPorts[1]); + newOutPorts.addAll(Arrays.asList(newNeighborPorts[0])); List toAddBuckets = new ArrayList(); for (PortNumber portNumber: newOutPorts) { @@ -258,79 +308,75 @@ public class SimpleGroupStoreTest { GroupEvent.Type.GROUP_UPDATE_REQUESTED); simpleGroupStore.setDelegate(updateGroupDescDelegate); simpleGroupStore.updateGroupDescription(D1, - key, + currKey, UpdateType.ADD, toAddGroupBuckets, addKey); simpleGroupStore.unsetDelegate(updateGroupDescDelegate); + } + + // Testing updateGroupDescription for REMOVE operation from northbound + private void testRemoveBuckets(TestGroupKey currKey, TestGroupKey removeKey) { + Group existingGroup = simpleGroupStore.getGroup(D1, currKey); + List buckets = new ArrayList(); + buckets.addAll(existingGroup.buckets().buckets()); - // Testing updateGroupDescription for REMOVE operation from northbound - TestGroupKey removeKey = new TestGroupKey("group1RemoveBuckets"); List toRemoveBuckets = new ArrayList(); - toRemoveBuckets.add(updatedGroupBuckets.buckets().get(0)); - toRemoveBuckets.add(updatedGroupBuckets.buckets().get(1)); + + // There should be 4 buckets in the current group + toRemoveBuckets.add(buckets.remove(0)); + toRemoveBuckets.add(buckets.remove(1)); GroupBuckets toRemoveGroupBuckets = new GroupBuckets(toRemoveBuckets); - List remainingBuckets = new ArrayList(); - remainingBuckets.add(updatedGroupBuckets.buckets().get(2)); - remainingBuckets.add(updatedGroupBuckets.buckets().get(3)); - GroupBuckets remainingGroupBuckets = new GroupBuckets(remainingBuckets); + + GroupBuckets remainingGroupBuckets = new GroupBuckets(buckets); InternalGroupStoreDelegate removeGroupDescDelegate = new InternalGroupStoreDelegate(removeKey, remainingGroupBuckets, GroupEvent.Type.GROUP_UPDATE_REQUESTED); simpleGroupStore.setDelegate(removeGroupDescDelegate); simpleGroupStore.updateGroupDescription(D1, - addKey, + currKey, UpdateType.REMOVE, toRemoveGroupBuckets, removeKey); simpleGroupStore.unsetDelegate(removeGroupDescDelegate); + } - // Testing getGroup operation - Group existingGroup = simpleGroupStore.getGroup(D1, removeKey); - checkStoreGroupDelegate.verifyGroupId(existingGroup.id()); - - // Testing addOrUpdateGroupEntry operation from southbound - InternalGroupStoreDelegate updateGroupEntryDelegate = - new InternalGroupStoreDelegate(removeKey, - remainingGroupBuckets, - GroupEvent.Type.GROUP_UPDATED); - simpleGroupStore.setDelegate(updateGroupEntryDelegate); - simpleGroupStore.addOrUpdateGroupEntry(existingGroup); - simpleGroupStore.unsetDelegate(updateGroupEntryDelegate); - - // Testing deleteGroupDescription operation from northbound + // Testing deleteGroupDescription operation from northbound + private void testDeleteGroup(TestGroupKey currKey) { + Group existingGroup = simpleGroupStore.getGroup(D1, currKey); InternalGroupStoreDelegate deleteGroupDescDelegate = - new InternalGroupStoreDelegate(removeKey, - remainingGroupBuckets, + new InternalGroupStoreDelegate(currKey, + existingGroup.buckets(), GroupEvent.Type.GROUP_REMOVE_REQUESTED); simpleGroupStore.setDelegate(deleteGroupDescDelegate); - simpleGroupStore.deleteGroupDescription(D1, removeKey); + simpleGroupStore.deleteGroupDescription(D1, currKey); simpleGroupStore.unsetDelegate(deleteGroupDescDelegate); + } - // Testing removeGroupEntry operation from southbound + // Testing removeGroupEntry operation from southbound + private void testRemoveGroupFromSB(TestGroupKey currKey) { + Group existingGroup = simpleGroupStore.getGroup(D1, currKey); InternalGroupStoreDelegate removeGroupEntryDelegate = - new InternalGroupStoreDelegate(removeKey, - remainingGroupBuckets, + new InternalGroupStoreDelegate(currKey, + existingGroup.buckets(), GroupEvent.Type.GROUP_REMOVED); simpleGroupStore.setDelegate(removeGroupEntryDelegate); simpleGroupStore.removeGroupEntry(existingGroup); // Testing getGroup operation - existingGroup = simpleGroupStore.getGroup(D1, removeKey); + existingGroup = simpleGroupStore.getGroup(D1, currKey); assertEquals(null, existingGroup); assertEquals(0, Iterables.size(simpleGroupStore.getGroups(D1))); assertEquals(0, simpleGroupStore.getGroupCount(D1)); simpleGroupStore.unsetDelegate(removeGroupEntryDelegate); - - } @Test public void testGroupOperationFailure() { - simpleGroupStore.deviceInitialAuditCompleted(D1); + simpleGroupStore.deviceInitialAuditCompleted(D1, true); ApplicationId appId = new DefaultApplicationId(2, "org.groupstore.test"); @@ -408,8 +454,6 @@ public class SimpleGroupStoreTest { GroupEvent.Type.GROUP_REMOVE_FAILED); simpleGroupStore.setDelegate(checkGroupDelFailureDelegate); simpleGroupStore.groupOperationFailed(D1, groupDelOp); - - } }