ONOS-685: Network Configuration Manager support for Segment Routing application

Change-Id: Ia15bfd24559dd5542633c8b76d500b2d31362340
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java
new file mode 100644
index 0000000..0ac3728
--- /dev/null
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java
@@ -0,0 +1,412 @@
+/*
+ * 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));
+    }
+
+}