blob: 0ac372849437c678961da203ec92d3bea317357a [file] [log] [blame]
/*
* 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.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
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.group.DefaultGroupBucket;
import org.onosproject.net.group.DefaultGroupDescription;
import org.onosproject.net.group.DefaultGroupKey;
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<Integer> allSegmentIds;
protected final int nodeSegmentId;
protected final boolean isEdgeRouter;
protected final MacAddress nodeMacAddr;
protected LinkService linkService;
protected GroupService groupService;
protected HashMap<DeviceId, Set<PortNumber>> devicePortMap =
new HashMap<DeviceId, Set<PortNumber>>();
protected HashMap<PortNumber, DeviceId> portDeviceMap =
new HashMap<PortNumber, DeviceId>();
private GroupListener listener = new InternalGroupListener();
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,
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<NeighborSet> 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);
if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) {
tBuilder.pushMpls()
.setMpls(MplsLabel.mplsLabel(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,
getGroupKey(ns),
removeBuckets,
getGroupKey(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<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;
}
protected void createGroupsFromNeighborsets(Set<NeighborSet> nsSet) {
for (NeighborSet ns : nsSet) {
// Create the bucket array from the neighbor set
List<GroupBucket> buckets = new ArrayList<GroupBucket>();
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);
if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) {
tBuilder.pushMpls()
.setMpls(MplsLabel.
mplsLabel(ns.getEdgeLabel()));
}
buckets.add(DefaultGroupBucket.createSelectGroupBucket(
tBuilder.build()));
}
}
GroupBuckets groupBuckets = new GroupBuckets(buckets);
GroupDescription newGroupDesc = new DefaultGroupDescription(
deviceId,
Group.Type.SELECT,
groupBuckets,
getGroupKey(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);
}
}
public GroupKey getGroupKey(Object obj) {
return new DefaultGroupKey(kryo.build().serialize(obj));
}
}