aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java')
-rw-r--r--framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java539
1 files changed, 539 insertions, 0 deletions
diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java
new file mode 100644
index 00000000..a43a0f09
--- /dev/null
+++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java
@@ -0,0 +1,539 @@
+/*
+ * 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.segmentrouting.grouphandler;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onlab.util.KryoNamespace;
+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.flowobjective.DefaultNextObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.NextObjective;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.ObjectiveContext;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.group.DefaultGroupKey;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.store.service.EventuallyConsistentMap;
+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 static final Logger log = getLogger(DefaultGroupHandler.class);
+
+ protected final DeviceId deviceId;
+ protected final ApplicationId appId;
+ protected final DeviceProperties deviceConfig;
+ protected final List<Integer> allSegmentIds;
+ protected final int nodeSegmentId;
+ protected final boolean isEdgeRouter;
+ protected final MacAddress nodeMacAddr;
+ protected LinkService linkService;
+ protected FlowObjectiveService flowObjectiveService;
+
+ protected HashMap<DeviceId, Set<PortNumber>> devicePortMap = new HashMap<>();
+ protected HashMap<PortNumber, DeviceId> portDeviceMap = new HashMap<>();
+ //protected HashMap<NeighborSet, Integer> deviceNextObjectiveIds =
+ // new HashMap<NeighborSet, Integer>();
+ protected EventuallyConsistentMap<
+ NeighborSetNextObjectiveStoreKey, Integer> nsNextObjStore = null;
+ protected Random rand = new Random();
+
+ protected KryoNamespace.Builder kryo = new KryoNamespace.Builder()
+ .register(URI.class).register(HashSet.class)
+ .register(DeviceId.class).register(PortNumber.class)
+ .register(NeighborSet.class).register(PolicyGroupIdentifier.class)
+ .register(PolicyGroupParams.class)
+ .register(GroupBucketIdentifier.class)
+ .register(GroupBucketIdentifier.BucketOutputType.class);
+
+ protected DefaultGroupHandler(DeviceId deviceId, ApplicationId appId,
+ DeviceProperties config,
+ LinkService linkService,
+ FlowObjectiveService flowObjService,
+ EventuallyConsistentMap<
+ NeighborSetNextObjectiveStoreKey,
+ Integer> nsNextObjStore) {
+ this.deviceId = checkNotNull(deviceId);
+ this.appId = checkNotNull(appId);
+ this.deviceConfig = checkNotNull(config);
+ this.linkService = checkNotNull(linkService);
+ allSegmentIds = checkNotNull(config.getAllDeviceSegmentIds());
+ nodeSegmentId = config.getSegmentId(deviceId);
+ isEdgeRouter = config.isEdgeDevice(deviceId);
+ nodeMacAddr = checkNotNull(config.getDeviceMac(deviceId));
+ this.flowObjectiveService = flowObjService;
+ this.nsNextObjStore = nsNextObjStore;
+
+ 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 flowObjService flow objective service object
+ * @param nsNextObjStore next objective store map
+ * @return default group handler type
+ */
+ public static DefaultGroupHandler createGroupHandler(DeviceId deviceId,
+ ApplicationId appId,
+ DeviceProperties config,
+ LinkService linkService,
+ FlowObjectiveService flowObjService,
+ EventuallyConsistentMap<NeighborSetNextObjectiveStoreKey,
+ Integer> nsNextObjStore) {
+ if (config.isEdgeDevice(deviceId)) {
+ return new DefaultEdgeGroupHandler(deviceId, appId, config,
+ linkService,
+ flowObjService,
+ nsNextObjStore);
+ } else {
+ return new DefaultTransitGroupHandler(deviceId, appId, config,
+ linkService,
+ flowObjService,
+ nsNextObjStore);
+ }
+ }
+
+ /**
+ * 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());
+ addNeighborAtPort(newLink.dst().deviceId(),
+ newLink.src().port());
+ /*if (devicePortMap.get(newLink.dst().deviceId()) == null) {
+ // New Neighbor
+ newNeighbor(newLink);
+ } else {
+ // Old Neighbor
+ newPortToExistingNeighbor(newLink);
+ }*/
+ Set<NeighborSet> nsSet = nsNextObjStore.keySet()
+ .stream()
+ .filter((nsStoreEntry) -> (nsStoreEntry.deviceId().equals(deviceId)))
+ .map((nsStoreEntry) -> (nsStoreEntry.neighborSet()))
+ .filter((ns) -> (ns.getDeviceIds()
+ .contains(newLink.dst().deviceId())))
+ .collect(Collectors.toSet());
+ log.trace("linkUp: nsNextObjStore contents for device {}:",
+ deviceId,
+ nsSet);
+ for (NeighborSet ns : nsSet) {
+ // Create the new bucket to be updated
+ TrafficTreatment.Builder tBuilder =
+ DefaultTrafficTreatment.builder();
+ tBuilder.setOutput(newLink.src().port())
+ .setEthDst(deviceConfig.getDeviceMac(
+ newLink.dst().deviceId()))
+ .setEthSrc(nodeMacAddr);
+ if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) {
+ tBuilder.pushMpls()
+ .setMpls(MplsLabel.
+ mplsLabel(ns.getEdgeLabel()));
+ }
+
+ Integer nextId = nsNextObjStore.
+ get(new NeighborSetNextObjectiveStoreKey(deviceId, ns));
+ if (nextId != null) {
+ NextObjective.Builder nextObjBuilder = DefaultNextObjective
+ .builder().withId(nextId)
+ .withType(NextObjective.Type.HASHED).fromApp(appId);
+
+ nextObjBuilder.addTreatment(tBuilder.build());
+
+ log.debug("linkUp in device {}: Adding Bucket "
+ + "with Port {} to next object id {}",
+ deviceId,
+ newLink.src().port(),
+ nextId);
+ NextObjective nextObjective = nextObjBuilder.
+ add(new SRNextObjectiveContext(deviceId));
+ flowObjectiveService.next(deviceId, nextObjective);
+ }
+ }
+ }
+
+ /**
+ * 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<NeighborSet> nsSet = computeImpactedNeighborsetForPortEvent(portDeviceMap
+ .get(port),
+ devicePortMap
+ .keySet());*/
+ Set<NeighborSet> nsSet = nsNextObjStore.keySet()
+ .stream()
+ .filter((nsStoreEntry) -> (nsStoreEntry.deviceId().equals(deviceId)))
+ .map((nsStoreEntry) -> (nsStoreEntry.neighborSet()))
+ .filter((ns) -> (ns.getDeviceIds()
+ .contains(portDeviceMap.get(port))))
+ .collect(Collectors.toSet());
+ log.trace("portDown: nsNextObjStore contents for device {}:",
+ deviceId,
+ nsSet);
+ 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);
+ if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) {
+ tBuilder.pushMpls().setMpls(MplsLabel.mplsLabel(ns
+ .getEdgeLabel()));
+ }
+
+ Integer nextId = nsNextObjStore.
+ get(new NeighborSetNextObjectiveStoreKey(deviceId, ns));
+ if (nextId != null) {
+ NextObjective.Builder nextObjBuilder = DefaultNextObjective
+ .builder().withType(NextObjective.Type.SIMPLE).withId(nextId).fromApp(appId);
+
+ nextObjBuilder.addTreatment(tBuilder.build());
+
+ log.debug("portDown in device {}: Removing Bucket "
+ + "with Port {} to next object id {}",
+ deviceId,
+ port,
+ nextId);
+ NextObjective nextObjective = nextObjBuilder.
+ remove(new SRNextObjectiveContext(deviceId));
+
+ flowObjectiveService.next(deviceId, nextObjective);
+ }
+
+ }
+
+ devicePortMap.get(portDeviceMap.get(port)).remove(port);
+ portDeviceMap.remove(port);
+ }
+
+ /**
+ * Returns the next objective associated with the neighborset.
+ * If there is no next objective for this neighborset, this API
+ * would create a next objective and return.
+ *
+ * @param ns neighborset
+ * @return int if found or -1
+ */
+ public int getNextObjectiveId(NeighborSet ns) {
+ Integer nextId = nsNextObjStore.
+ get(new NeighborSetNextObjectiveStoreKey(deviceId, ns));
+ if (nextId == null) {
+ log.trace("getNextObjectiveId in device{}: Next objective id "
+ + "not found for {} and creating", deviceId, ns);
+ log.trace("getNextObjectiveId: nsNextObjStore contents for device {}: {}",
+ deviceId,
+ nsNextObjStore.entrySet()
+ .stream()
+ .filter((nsStoreEntry) ->
+ (nsStoreEntry.getKey().deviceId().equals(deviceId)))
+ .collect(Collectors.toList()));
+ createGroupsFromNeighborsets(Collections.singleton(ns));
+ nextId = nsNextObjStore.
+ get(new NeighborSetNextObjectiveStoreKey(deviceId, ns));
+ if (nextId == null) {
+ log.warn("getNextObjectiveId: unable to create next objective");
+ return -1;
+ } else {
+ log.debug("getNextObjectiveId in device{}: Next objective id {} "
+ + "created for {}", deviceId, nextId.intValue(), ns);
+ }
+ } else {
+ log.trace("getNextObjectiveId in device{}: Next objective id {} "
+ + "found for {}", deviceId, nextId.intValue(), ns);
+ }
+ return nextId.intValue();
+ }
+
+ /**
+ * Checks if the next objective ID (group) for the neighbor set exists or not.
+ *
+ * @param ns neighbor set to check
+ * @return true if it exists, false otherwise
+ */
+ public boolean hasNextObjectiveId(NeighborSet ns) {
+ Integer nextId = nsNextObjStore.
+ get(new NeighborSetNextObjectiveStoreKey(deviceId, ns));
+ if (nextId == null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ // Empty implementation
+ protected void newNeighbor(Link newLink) {
+ }
+
+ // Empty implementation
+ protected void newPortToExistingNeighbor(Link newLink) {
+ }
+
+ // Empty implementation
+ protected Set<NeighborSet>
+ computeImpactedNeighborsetForPortEvent(DeviceId impactedNeighbor,
+ Set<DeviceId> updatedNeighbors) {
+ return null;
+ }
+
+ private void populateNeighborMaps() {
+ Set<Link> 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<PortNumber> ports = new HashSet<PortNumber>();
+ ports.add(portToNeighbor);
+ devicePortMap.put(neighborId, ports);
+ }
+
+ // Update portToDevice database
+ if (portDeviceMap.get(portToNeighbor) == null) {
+ portDeviceMap.put(portToNeighbor, neighborId);
+ }
+ }
+
+ protected Set<Set<DeviceId>> getPowerSetOfNeighbors(Set<DeviceId> neighbors) {
+ List<DeviceId> list = new ArrayList<DeviceId>(neighbors);
+ Set<Set<DeviceId>> sets = new HashSet<Set<DeviceId>>();
+ // 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<DeviceId> neighborSubSet = new HashSet<DeviceId>();
+ 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<Integer> getSegmentIdsTobePairedWithNeighborSet(Set<DeviceId> neighbors) {
+
+ List<Integer> nsSegmentIds = new ArrayList<Integer>();
+
+ // Always pair up with no edge label
+ // 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;
+ }
+
+ /**
+ * Creates Groups from a set of NeighborSet given.
+ *
+ * @param nsSet a set of NeighborSet
+ */
+ public void createGroupsFromNeighborsets(Set<NeighborSet> nsSet) {
+ for (NeighborSet ns : nsSet) {
+ int nextId = flowObjectiveService.allocateNextId();
+ NextObjective.Builder nextObjBuilder = DefaultNextObjective
+ .builder().withId(nextId)
+ .withType(NextObjective.Type.HASHED).fromApp(appId);
+ for (DeviceId d : ns.getDeviceIds()) {
+ if (devicePortMap.get(d) == null) {
+ log.warn("Device {} is not in the port map yet", d);
+ return;
+ } else if (devicePortMap.get(d).size() == 0) {
+ log.warn("There are no ports for "
+ + "the Device {} in the port map yet", d);
+ return;
+ }
+
+ for (PortNumber sp : devicePortMap.get(d)) {
+ TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment
+ .builder();
+ tBuilder.setOutput(sp)
+ .setEthDst(deviceConfig.getDeviceMac(d))
+ .setEthSrc(nodeMacAddr);
+ if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) {
+ tBuilder.pushMpls().setMpls(MplsLabel.mplsLabel(ns
+ .getEdgeLabel()));
+ }
+ nextObjBuilder.addTreatment(tBuilder.build());
+ }
+ }
+
+ NextObjective nextObj = nextObjBuilder.
+ add(new SRNextObjectiveContext(deviceId));
+ flowObjectiveService.next(deviceId, nextObj);
+ log.debug("createGroupsFromNeighborsets: Submited "
+ + "next objective {} in device {}",
+ nextId, deviceId);
+ nsNextObjStore.put(new NeighborSetNextObjectiveStoreKey(deviceId, ns),
+ nextId);
+ }
+ }
+
+ public GroupKey getGroupKey(Object obj) {
+ return new DefaultGroupKey(kryo.build().serialize(obj));
+ }
+
+ /**
+ * Removes groups for the next objective ID given.
+ *
+ * @param objectiveId next objective ID to remove
+ * @return true if succeeds, false otherwise
+ */
+ public boolean removeGroup(int objectiveId) {
+
+ if (nsNextObjStore.containsValue(objectiveId)) {
+ NextObjective.Builder nextObjBuilder = DefaultNextObjective
+ .builder().withId(objectiveId)
+ .withType(NextObjective.Type.HASHED).fromApp(appId);
+ NextObjective nextObjective = nextObjBuilder.
+ remove(new SRNextObjectiveContext(deviceId));
+ flowObjectiveService.next(deviceId, nextObjective);
+
+ for (Map.Entry<NeighborSetNextObjectiveStoreKey, Integer> entry: nsNextObjStore.entrySet()) {
+ if (entry.getValue().equals(objectiveId)) {
+ nsNextObjStore.remove(entry.getKey());
+ break;
+ }
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ protected static class SRNextObjectiveContext implements ObjectiveContext {
+ final DeviceId deviceId;
+
+ SRNextObjectiveContext(DeviceId deviceId) {
+ this.deviceId = deviceId;
+ }
+ @Override
+ public void onSuccess(Objective objective) {
+ log.debug("Next objective operation successful in device {}",
+ deviceId);
+ }
+
+ @Override
+ public void onError(Objective objective, ObjectiveError error) {
+ log.warn("Next objective {} operation failed with error: {} in device {}",
+ objective, error, deviceId);
+ }
+ }
+}