ONOS-685: Network Configuration Manager support for Segment Routing application
Change-Id: Ia15bfd24559dd5542633c8b76d500b2d31362340
diff --git a/src/main/java/org/onosproject/segmentrouting/ArpHandler.java b/src/main/java/org/onosproject/segmentrouting/ArpHandler.java
index 5b94518..b696525 100644
--- a/src/main/java/org/onosproject/segmentrouting/ArpHandler.java
+++ b/src/main/java/org/onosproject/segmentrouting/ArpHandler.java
@@ -19,7 +19,6 @@
import org.onlab.packet.Ethernet;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.IpAddress;
-import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
@@ -36,7 +35,7 @@
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
-
+import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
public class ArpHandler {
@@ -112,11 +111,11 @@
private boolean isArpReqForRouter(DeviceId deviceId, ARP arpRequest) {
- Ip4Address gatewayIpAddress = config.getGatewayIpAddress(deviceId);
- if (gatewayIpAddress != null) {
+ List<Ip4Address> gatewayIpAddresses = config.getGatewayIpAddress(deviceId);
+ if (gatewayIpAddresses != null) {
Ip4Address targetProtocolAddress = Ip4Address.valueOf(arpRequest
.getTargetProtocolAddress());
- if (gatewayIpAddress.equals(targetProtocolAddress)) {
+ if (gatewayIpAddresses.contains(targetProtocolAddress)) {
return true;
}
}
@@ -124,15 +123,11 @@
}
private boolean isArpReqForSubnet(DeviceId deviceId, ARP arpRequest) {
- String subnetInfo = config.getSubnetInfo(deviceId);
- if (subnetInfo != null) {
- IpPrefix prefix = IpPrefix.valueOf(subnetInfo);
- if (prefix.contains(Ip4Address.valueOf(arpRequest.getTargetProtocolAddress()))) {
- return true;
- }
- }
-
- return false;
+ return config.getSubnetInfo(deviceId).stream()
+ .anyMatch((prefix)->
+ prefix.contains(Ip4Address.
+ valueOf(arpRequest.
+ getTargetProtocolAddress())));
}
/**
diff --git a/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java b/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
index 3684ec9..58d3262 100644
--- a/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
+++ b/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
@@ -15,6 +15,7 @@
*/
package org.onosproject.segmentrouting;
+import org.onlab.packet.Ip4Prefix;
import org.onlab.packet.IpPrefix;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
@@ -26,6 +27,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
@@ -139,8 +141,11 @@
// If both target switch and dest switch are edge routers, then set IP rule
// for both subnet and router IP.
if (config.isEdgeRouter(targetSw) && config.isEdgeRouter(destSw)) {
- String subnets = config.getSubnetInfo(destSw);
- result = rulePopulator.populateIpRuleForSubnet(targetSw, subnets, destSw, nextHops);
+ List<Ip4Prefix> subnets = config.getSubnetInfo(destSw);
+ result = rulePopulator.populateIpRuleForSubnet(targetSw,
+ subnets,
+ destSw,
+ nextHops);
if (!result) {
return false;
}
diff --git a/src/main/java/org/onosproject/segmentrouting/DeviceConfiguration.java b/src/main/java/org/onosproject/segmentrouting/DeviceConfiguration.java
index 17cfd96..0dfb3fd 100644
--- a/src/main/java/org/onosproject/segmentrouting/DeviceConfiguration.java
+++ b/src/main/java/org/onosproject/segmentrouting/DeviceConfiguration.java
@@ -1,171 +1,218 @@
package org.onosproject.segmentrouting;
import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
-import org.onosproject.grouphandler.DeviceProperties;
+import org.onosproject.segmentrouting.grouphandler.DeviceProperties;
import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.segmentrouting.config.NetworkConfig.SwitchConfig;
+import org.onosproject.segmentrouting.config.NetworkConfigManager;
+import org.onosproject.segmentrouting.config.SegmentRouterConfig;
+import org.onosproject.segmentrouting.config.SegmentRouterConfig.Subnet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.Arrays;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+/**
+ * Segment Routing configuration component that reads the
+ * segment routing related configuration from Network Configuration Manager
+ * component and organizes in more accessible formats.
+ *
+ * TODO: Merge multiple Segment Routing configuration wrapper classes into one.
+ */
public class DeviceConfiguration implements DeviceProperties {
private static final Logger log = LoggerFactory
.getLogger(DeviceConfiguration.class);
- private final List<Integer> allSegmentIds =
- Arrays.asList(101, 102, 103, 104, 105, 106);
- private HashMap<DeviceId, Integer> deviceSegmentIdMap =
- new HashMap<DeviceId, Integer>() {
- {
- 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<DeviceId, MacAddress> deviceMacMap =
- new HashMap<DeviceId, MacAddress>() {
- {
- 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"));
- }
- };
+ private final List<Integer> allSegmentIds = new ArrayList<Integer>();
+ private final HashMap<DeviceId, SegmentRouterInfo> deviceConfigMap = new HashMap<>();
+ private final NetworkConfigManager configService;
- private final HashMap<DeviceId, Ip4Address> deviceIpMap =
- new HashMap<DeviceId, Ip4Address>() {
- {
- put(DeviceId.deviceId("of:0000000000000001"),
- Ip4Address.valueOf("192.168.0.1"));
- put(DeviceId.deviceId("of:0000000000000002"),
- Ip4Address.valueOf("192.168.0.2"));
- put(DeviceId.deviceId("of:0000000000000003"),
- Ip4Address.valueOf("192.168.0.3"));
- put(DeviceId.deviceId("of:0000000000000004"),
- Ip4Address.valueOf("192.168.0.4"));
- put(DeviceId.deviceId("of:0000000000000005"),
- Ip4Address.valueOf("192.168.0.5"));
- put(DeviceId.deviceId("of:0000000000000006"),
- Ip4Address.valueOf("192.168.0.6"));
- }
- };
+ private class SegmentRouterInfo {
+ int nodeSid;
+ DeviceId deviceId;
+ Ip4Address ip;
+ MacAddress mac;
+ boolean isEdge;
+ HashMap<PortNumber, Ip4Address> gatewayIps;
+ HashMap<PortNumber, Ip4Prefix> subnets;
+ }
+ /**
+ * Constructor. Reads all the configuration for all devices of type
+ * Segment Router and organizes into various maps for easier access.
+ *
+ * @param configService handle to network configuration manager
+ * component from where the relevant configuration is retrieved.
+ */
+ public DeviceConfiguration(NetworkConfigManager configService) {
+ this.configService = checkNotNull(configService);
+ List<SwitchConfig> allSwitchCfg =
+ this.configService.getConfiguredAllowedSwitches();
+ for (SwitchConfig cfg : allSwitchCfg) {
+ if (!(cfg instanceof SegmentRouterConfig)) {
+ continue;
+ }
+ SegmentRouterInfo info = new SegmentRouterInfo();
+ info.nodeSid = ((SegmentRouterConfig) cfg).getNodeSid();
+ info.deviceId = ((SegmentRouterConfig) cfg).getDpid();
+ info.mac = MacAddress.valueOf(((
+ SegmentRouterConfig) cfg).getRouterMac());
+ String routerIp = ((SegmentRouterConfig) cfg).getRouterIp();
+ Ip4Prefix prefix = checkNotNull(IpPrefix.valueOf(routerIp).getIp4Prefix());
+ info.ip = prefix.address();
+ info.isEdge = ((SegmentRouterConfig) cfg).isEdgeRouter();
+ info.subnets = new HashMap<>();
+ info.gatewayIps = new HashMap<PortNumber, Ip4Address>();
+ for (Subnet s: ((SegmentRouterConfig) cfg).getSubnets()) {
+ info.subnets.put(PortNumber.portNumber(s.getPortNo()),
+ Ip4Prefix.valueOf(s.getSubnetIp()));
+ String gatewayIp = s.getSubnetIp().
+ substring(0, s.getSubnetIp().indexOf('/'));
+ info.gatewayIps.put(PortNumber.portNumber(s.getPortNo()),
+ Ip4Address.valueOf(gatewayIp));
+ }
+ this.deviceConfigMap.put(info.deviceId, info);
+ this.allSegmentIds.add(info.nodeSid);
+ }
+ }
+
+ /**
+ * Returns the segment id of a segment router.
+ *
+ * @param deviceId device identifier
+ * @return segment id
+ */
@Override
public int getSegmentId(DeviceId deviceId) {
- if (deviceSegmentIdMap.get(deviceId) != null) {
+ if (deviceConfigMap.get(deviceId) != null) {
log.debug("getSegmentId for device{} is {}",
deviceId,
- deviceSegmentIdMap.get(deviceId));
- return deviceSegmentIdMap.get(deviceId);
+ deviceConfigMap.get(deviceId).nodeSid);
+ return deviceConfigMap.get(deviceId).nodeSid;
} else {
throw new IllegalStateException();
}
}
+ /**
+ * Returns the segment id of a segment router given its mac address.
+ *
+ * @param routerMac router mac address
+ * @return segment id
+ */
+ public int getSegmentId(MacAddress routerMac) {
+ for (Map.Entry<DeviceId, SegmentRouterInfo> entry:
+ deviceConfigMap.entrySet()) {
+ if (entry.getValue().mac.equals(routerMac)) {
+ return entry.getValue().nodeSid;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the segment id of a segment router given its router ip address.
+ *
+ * @param routerAddress router ip address
+ * @return segment id
+ */
+ public int getSegmentId(Ip4Address routerAddress) {
+ for (Map.Entry<DeviceId, SegmentRouterInfo> entry:
+ deviceConfigMap.entrySet()) {
+ if (entry.getValue().ip.equals(routerAddress)) {
+ return entry.getValue().nodeSid;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Returns the router mac of a segment router.
+ *
+ * @param deviceId device identifier
+ * @return router mac address
+ */
@Override
public MacAddress getDeviceMac(DeviceId deviceId) {
- if (deviceMacMap.get(deviceId) != null) {
+ if (deviceConfigMap.get(deviceId) != null) {
log.debug("getDeviceMac for device{} is {}",
deviceId,
- deviceMacMap.get(deviceId));
- return deviceMacMap.get(deviceId);
+ deviceConfigMap.get(deviceId).mac);
+ return deviceConfigMap.get(deviceId).mac;
} else {
throw new IllegalStateException();
}
}
-
- @Override
- public boolean isEdgeDevice(DeviceId deviceId) {
- if (deviceId.equals(DeviceId.deviceId("of:0000000000000001"))
- || deviceId.equals(DeviceId.deviceId("of:0000000000000006"))) {
- return true;
+ /**
+ * Returns the router ip address of a segment router.
+ *
+ * @param deviceId device identifier
+ * @return router ip address
+ */
+ public Ip4Address getRouterIp(DeviceId deviceId) {
+ if (deviceConfigMap.get(deviceId) != null) {
+ log.debug("getDeviceIp for device{} is {}",
+ deviceId,
+ deviceConfigMap.get(deviceId).ip);
+ return deviceConfigMap.get(deviceId).ip;
+ } else {
+ throw new IllegalStateException();
}
-
- return false;
}
+ /**
+ * Indicates if the segment router is a edge router or
+ * a transit/back bone router.
+ *
+ * @param deviceId device identifier
+ * @return boolean
+ */
+ @Override
+ public boolean isEdgeDevice(DeviceId deviceId) {
+ if (deviceConfigMap.get(deviceId) != null) {
+ log.debug("isEdgeDevice for device{} is {}",
+ deviceId,
+ deviceConfigMap.get(deviceId).isEdge);
+ return deviceConfigMap.get(deviceId).isEdge;
+ } else {
+ throw new IllegalStateException();
+ }
+ }
+
+ /**
+ * Returns the segment ids of all configured segment routers.
+ *
+ * @return list of segment ids
+ */
@Override
public List<Integer> getAllDeviceSegmentIds() {
return allSegmentIds;
}
-
/**
- * Returns Segment ID for the router with the MAC address given.
+ * Returns the device identifier or data plane identifier (dpid)
+ * of a segment router given its segment id.
*
- * @param targetMac Mac address for the router
- * @return Segment ID for the router with the MAC address
- */
- public int getSegmentId(MacAddress targetMac) {
- for (Map.Entry<DeviceId, MacAddress> entry: deviceMacMap.entrySet()) {
- if (entry.getValue().equals(targetMac)) {
- return deviceSegmentIdMap.get(entry.getKey());
- }
- }
-
- return -1;
- }
-
- /**
- * Returns Segment ID for the router withe IP address given.
- *
- * @param targetAddress IP address of the router
- * @return Segment ID for the router with the IP address
- */
- public int getSegmentId(Ip4Address targetAddress) {
- for (Map.Entry<DeviceId, Ip4Address> entry: deviceIpMap.entrySet()) {
- if (entry.getValue().equals(targetAddress)) {
- return deviceSegmentIdMap.get(entry.getKey());
- }
- }
-
- return -1;
- }
-
- /**
- * Returns Router IP address for the router with the device ID given.
- *
- * @param deviceId device ID of the router
- * @return IP address of the router
- */
- public Ip4Address getRouterIp(DeviceId deviceId) {
- if (deviceIpMap.get(deviceId) != null) {
- log.debug("getDeviceIp for device{} is {}",
- deviceId,
- deviceIpMap.get(deviceId));
- return deviceIpMap.get(deviceId);
- } else {
- throw new IllegalStateException();
- }
- }
-
- /**
- * Returns the Device ID of the router with the Segment ID given.
- *
- * @param sid Segment ID of the router
- * @return Device ID of the router
+ * @param sid segment id
+ * @return deviceId device identifier
*/
public DeviceId getDeviceId(int sid) {
- for (Map.Entry<DeviceId, Integer> entry: deviceSegmentIdMap.entrySet()) {
- if (entry.getValue() == sid) {
- return entry.getKey();
+ for (Map.Entry<DeviceId, SegmentRouterInfo> entry:
+ deviceConfigMap.entrySet()) {
+ if (entry.getValue().nodeSid == sid) {
+ return entry.getValue().deviceId;
}
}
@@ -173,18 +220,97 @@
}
/**
- * Returns the Device ID of the router with the IP address given.
+ * Returns the device identifier or data plane identifier (dpid)
+ * of a segment router given its router ip address.
*
- * @param ipAddress IP address of the router
- * @return Device ID of the router
+ * @param ipAddress router ip address
+ * @return deviceId device identifier
*/
public DeviceId getDeviceId(Ip4Address ipAddress) {
- for (Map.Entry<DeviceId, Ip4Address> entry: deviceIpMap.entrySet()) {
- if (entry.getValue().equals(ipAddress)) {
- return entry.getKey();
+ for (Map.Entry<DeviceId, SegmentRouterInfo> entry:
+ deviceConfigMap.entrySet()) {
+ if (entry.getValue().ip.equals(ipAddress)) {
+ return entry.getValue().deviceId;
}
}
return null;
}
+
+ /**
+ * Returns the configured subnet gateway ip addresses for a segment router.
+ *
+ * @param deviceId device identifier
+ * @return list of ip addresses
+ */
+ public List<Ip4Address> getSubnetGatewayIps(DeviceId deviceId) {
+ if (deviceConfigMap.get(deviceId) != null) {
+ log.debug("getSubnetGatewayIps for device{} is {}",
+ deviceId,
+ deviceConfigMap.get(deviceId).gatewayIps.values());
+ return new ArrayList<Ip4Address>(deviceConfigMap.
+ get(deviceId).gatewayIps.values());
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the configured subnet prefixes for a segment router.
+ *
+ * @param deviceId device identifier
+ * @return list of ip prefixes
+ */
+ public List<Ip4Prefix> getSubnets(DeviceId deviceId) {
+ if (deviceConfigMap.get(deviceId) != null) {
+ log.debug("getSubnets for device{} is {}",
+ deviceId,
+ deviceConfigMap.get(deviceId).subnets.values());
+ return new ArrayList<Ip4Prefix>(deviceConfigMap.
+ get(deviceId).subnets.values());
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the router ip address of segment router that has the
+ * specified ip address in its subnets.
+ *
+ * @param destIpAddress target ip address
+ * @return router ip address
+ */
+ public Ip4Address getRouterIpAddressForASubnetHost(Ip4Address destIpAddress) {
+ for (Map.Entry<DeviceId, SegmentRouterInfo> entry:
+ deviceConfigMap.entrySet()) {
+ for (Ip4Prefix prefix:entry.getValue().subnets.values()) {
+ if (prefix.contains(destIpAddress)) {
+ return entry.getValue().ip;
+ }
+ }
+ }
+
+ log.debug("No router was found for {}", destIpAddress);
+ return null;
+ }
+
+ /**
+ * Returns the router mac address of segment router that has the
+ * specified ip address as one of its subnet gateway ip address.
+ *
+ * @param gatewayIpAddress router gateway ip address
+ * @return router mac address
+ */
+ public MacAddress getRouterMacForAGatewayIp(Ip4Address gatewayIpAddress) {
+ for (Map.Entry<DeviceId, SegmentRouterInfo> entry:
+ deviceConfigMap.entrySet()) {
+ if (entry.getValue().gatewayIps.
+ values().contains(gatewayIpAddress)) {
+ return entry.getValue().mac;
+ }
+ }
+
+ log.debug("Cannot find a router for {}", gatewayIpAddress);
+ return null;
+ }
}
diff --git a/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java b/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
index 3bfb888..45fa794 100644
--- a/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
+++ b/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
@@ -16,7 +16,7 @@
package org.onosproject.segmentrouting;
import java.nio.ByteBuffer;
-
+import java.util.List;
import org.onlab.packet.Ethernet;
import org.onlab.packet.ICMP;
import org.onlab.packet.IPv4;
@@ -69,14 +69,14 @@
DeviceId deviceId = connectPoint.deviceId();
Ip4Address destinationAddress =
Ip4Address.valueOf(ipv4.getDestinationAddress());
- Ip4Address gatewayIpAddress = config.getGatewayIpAddress(deviceId);
+ List<Ip4Address> gatewayIpAddresses = config.getGatewayIpAddress(deviceId);
IpPrefix routerIpPrefix = config.getRouterIpAddress(deviceId);
Ip4Address routerIpAddress = routerIpPrefix.getIp4Prefix().address();
// ICMP to the router IP or gateway IP
if (((ICMP) ipv4.getPayload()).getIcmpType() == ICMP.TYPE_ECHO_REQUEST &&
(destinationAddress.equals(routerIpAddress) ||
- gatewayIpAddress.equals(destinationAddress))) {
+ gatewayIpAddresses.contains(destinationAddress))) {
sendICMPResponse(ethernet, connectPoint);
// TODO: do we need to set the flow rule again ??
diff --git a/src/main/java/org/onosproject/segmentrouting/NetworkConfigHandler.java b/src/main/java/org/onosproject/segmentrouting/NetworkConfigHandler.java
index 2cb5867..ad210bb 100644
--- a/src/main/java/org/onosproject/segmentrouting/NetworkConfigHandler.java
+++ b/src/main/java/org/onosproject/segmentrouting/NetworkConfigHandler.java
@@ -16,6 +16,7 @@
package org.onosproject.segmentrouting;
import com.google.common.collect.Lists;
+
import org.onlab.packet.Ip4Address;
import org.onlab.packet.Ip4Prefix;
import org.onlab.packet.IpPrefix;
@@ -26,39 +27,33 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.net.URI;
import java.util.List;
import java.util.Set;
/**
* This class is temporary class and used only for test.
* It will be replaced with "real" Network Config Manager.
+ *
+ * TODO: Knock off this wrapper and directly use DeviceConfiguration class
*/
public class NetworkConfigHandler {
private static Logger log = LoggerFactory.getLogger(NetworkConfigHandler.class);
private SegmentRoutingManager srManager;
- private DeviceConfiguration deviceConfig = new DeviceConfiguration();
+ private DeviceConfiguration deviceConfig;
- public NetworkConfigHandler(SegmentRoutingManager srManager) {
+ public NetworkConfigHandler(SegmentRoutingManager srManager,
+ DeviceConfiguration deviceConfig) {
this.srManager = srManager;
+ this.deviceConfig = deviceConfig;
}
- public Ip4Address getGatewayIpAddress(DeviceId deviceId) {
-
- if (deviceId.uri().equals(URI.create("of:0000000000000001"))) {
- return Ip4Address.valueOf("10.0.1.128");
- } else if (deviceId.uri().equals(URI.create("of:0000000000000006"))) {
- return Ip4Address.valueOf("7.7.7.128");
- }
-
- log.warn("No gateway Ip address was found for {}", deviceId);
- return Ip4Address.valueOf("0.0.0.0");
+ public List<Ip4Address> getGatewayIpAddress(DeviceId deviceId) {
+ return this.deviceConfig.getSubnetGatewayIps(deviceId);
}
public IpPrefix getRouterIpAddress(DeviceId deviceId) {
-
return IpPrefix.valueOf(deviceConfig.getRouterIp(deviceId), 32);
}
@@ -68,17 +63,13 @@
public boolean inSameSubnet(DeviceId deviceId, Ip4Address destIp) {
- String subnetInfo = getSubnetInfo(deviceId);
- if (subnetInfo == null) {
+ List<Ip4Prefix> subnets = getSubnetInfo(deviceId);
+ if (subnets == null) {
return false;
}
- IpPrefix prefix = IpPrefix.valueOf(subnetInfo);
- if (prefix.contains(destIp)) {
- return true;
- }
-
- return false;
+ return subnets.stream()
+ .anyMatch((subnet) -> subnet.contains(destIp));
}
public boolean inSameSubnet(Ip4Address address, int sid) {
@@ -88,43 +79,23 @@
return false;
}
- String subnetInfo = getSubnetInfo(deviceId);
- if (subnetInfo == null) {
- log.warn("Cannot find the subnet info for {}", deviceId);
- return false;
- }
-
- Ip4Prefix subnet = Ip4Prefix.valueOf(subnetInfo);
- if (subnet.contains(address)) {
- return true;
- }
-
- return false;
-
+ return inSameSubnet(deviceId, address);
}
- public String getSubnetInfo(DeviceId deviceId) {
- // TODO : supports multiple subnet
- if (deviceId.uri().equals(URI.create("of:0000000000000001"))) {
- return "10.0.1.1/24";
- } else if (deviceId.uri().equals(URI.create("of:0000000000000006"))) {
- return "7.7.7.7/24";
- } else {
- log.error("Switch {} is not an edge router", deviceId);
- return null;
- }
+ public List<Ip4Prefix> getSubnetInfo(DeviceId deviceId) {
+ return deviceConfig.getSubnets(deviceId);
}
public int getMplsId(DeviceId deviceId) {
return deviceConfig.getSegmentId(deviceId);
}
- public int getMplsId(MacAddress mac) {
- return deviceConfig.getSegmentId(mac);
+ public int getMplsId(MacAddress routerMac) {
+ return deviceConfig.getSegmentId(routerMac);
}
- public int getMplsId(Ip4Address address) {
- return deviceConfig.getSegmentId(address);
+ public int getMplsId(Ip4Address routerIpAddress) {
+ return deviceConfig.getSegmentId(routerIpAddress);
}
public boolean isEcmpNotSupportedInTransit(DeviceId deviceId) {
@@ -132,17 +103,12 @@
}
public boolean isTransitRouter(DeviceId deviceId) {
- return true;
+ return !(deviceConfig.isEdgeDevice(deviceId));
}
public boolean isEdgeRouter(DeviceId deviceId) {
- if (deviceId.uri().equals(URI.create("of:0000000000000001"))
- || deviceId.uri().equals(URI.create("of:0000000000000006"))) {
- return true;
- }
-
- return false;
+ return deviceConfig.isEdgeDevice(deviceId);
}
private List<PortNumber> getPortsToNeighbors(DeviceId deviceId, List<DeviceId> fwdSws) {
@@ -177,16 +143,7 @@
public Ip4Address getDestinationRouterAddress(Ip4Address destIpAddress) {
- // TODO: need to check the subnet info
- if (destIpAddress.toString().equals("10.0.1.1")) {
- return Ip4Address.valueOf("192.168.0.1");
- } else if (destIpAddress.toString().equals("7.7.7.7")) {
- return Ip4Address.valueOf("192.168.0.6");
- } else {
- log.warn("No router was found for {}", destIpAddress);
- return null;
- }
-
+ return deviceConfig.getRouterIpAddressForASubnetHost(destIpAddress);
}
public DeviceId getDeviceId(Ip4Address ip4Address) {
@@ -194,13 +151,6 @@
}
public MacAddress getRouterMac(Ip4Address targetAddress) {
- if (targetAddress.toString().equals("10.0.1.128")) {
- return MacAddress.valueOf("00:00:00:00:00:01");
- } else if (targetAddress.toString().equals("7.7.7.128")) {
- return MacAddress.valueOf("00:00:00:00:00:06");
- } else {
- log.warn("Cannot find a router for {}", targetAddress);
- return null;
- }
+ return deviceConfig.getRouterMacForAGatewayIp(targetAddress);
}
}
diff --git a/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java b/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
index 063517e..337b1f3 100644
--- a/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
+++ b/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
@@ -17,11 +17,11 @@
import org.onlab.packet.Ethernet;
import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
-
import org.onlab.packet.MplsLabel;
-import org.onosproject.grouphandler.NeighborSet;
+import org.onosproject.segmentrouting.grouphandler.NeighborSet;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Link;
import org.onosproject.net.PortNumber;
@@ -94,15 +94,15 @@
* Populates IP flow rules for the subnets of the destination router.
*
* @param deviceId switch ID to set the rules
- * @param subnetInfo subnet information
+ * @param subnets subnet information
* @param destSw destination switch ID
* @param nextHops next hop switch ID list
* @return true if all rules are set successfully, false otherwise
*/
- public boolean populateIpRuleForSubnet(DeviceId deviceId, String subnetInfo,
+ public boolean populateIpRuleForSubnet(DeviceId deviceId, List<Ip4Prefix> subnets,
DeviceId destSw, Set<DeviceId> nextHops) {
- List<IpPrefix> subnets = extractSubnet(subnetInfo);
+ //List<IpPrefix> subnets = extractSubnet(subnetInfo);
for (IpPrefix subnet: subnets) {
if (!populateIpRuleForRouter(deviceId, subnet, destSw, nextHops)) {
return false;
@@ -371,21 +371,6 @@
}
-
- private List<IpPrefix> extractSubnet(String subnetInfo) {
- List<IpPrefix> subnetIpPrefixes = new ArrayList<>();
-
- // TODO: refactoring required depending on the format of the subnet info
- IpPrefix prefix = IpPrefix.valueOf(subnetInfo);
- if (prefix == null) {
- log.error("Wrong ip prefix type {}", subnetInfo);
- } else {
- subnetIpPrefixes.add(prefix);
- }
-
- return subnetIpPrefixes;
- }
-
private Link selectOneLink(DeviceId srcId, Set<DeviceId> destIds) {
Set<Link> links = srManager.linkService.getDeviceEgressLinks(srcId);
diff --git a/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index a7a3b11..b5e1181 100644
--- a/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -25,8 +25,8 @@
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.event.Event;
-import org.onosproject.grouphandler.DefaultGroupHandler;
-import org.onosproject.grouphandler.NeighborSet;
+import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler;
+import org.onosproject.segmentrouting.grouphandler.NeighborSet;
import org.onosproject.mastership.MastershipService;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
@@ -52,6 +52,7 @@
import org.onosproject.net.packet.PacketProcessor;
import org.onosproject.net.packet.PacketService;
import org.onosproject.net.topology.TopologyService;
+import org.onosproject.segmentrouting.config.NetworkConfigManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -98,16 +99,15 @@
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected MastershipService mastershipService;
-
- protected NetworkConfigHandler networkConfigHandler = new NetworkConfigHandler(this);
- protected ArpHandler arpHandler = new ArpHandler(this);
- protected IcmpHandler icmpHandler = new IcmpHandler(this);
- protected IpHandler ipHandler = new IpHandler(this);
- protected RoutingRulePopulator routingRulePopulator = new RoutingRulePopulator(this);
+ protected NetworkConfigHandler networkConfigHandler = null;
+ protected ArpHandler arpHandler = null;
+ protected IcmpHandler icmpHandler = null;
+ protected IpHandler ipHandler = null;
+ protected RoutingRulePopulator routingRulePopulator = null;
protected ApplicationId appId;
- private DefaultRoutingHandler defaultRoutingHandler = new DefaultRoutingHandler(this);
- private DeviceConfiguration deviceConfiguration = new DeviceConfiguration();
+ private DefaultRoutingHandler defaultRoutingHandler = null;
+ private DeviceConfiguration deviceConfiguration = null;
private InternalPacketProcessor processor = new InternalPacketProcessor();
private InternalEventHandler eventHandler = new InternalEventHandler();
@@ -118,6 +118,8 @@
private Map<DeviceId, DefaultGroupHandler> groupHandlerMap
= new ConcurrentHashMap<DeviceId, DefaultGroupHandler>();
+ private NetworkConfigManager networkConfigService = new NetworkConfigManager();;
+
private static int numOfEvents = 0;
private static int numOfHandlerExecution = 0;
private static int numOfHandlerScheduled = 0;
@@ -125,6 +127,16 @@
@Activate
protected void activate() {
appId = coreService.registerApplication("org.onosproject.segmentrouting");
+ networkConfigService.init();
+ deviceConfiguration = new DeviceConfiguration(networkConfigService);
+ networkConfigHandler = new NetworkConfigHandler(this,
+ deviceConfiguration);
+ arpHandler = new ArpHandler(this);
+ icmpHandler = new IcmpHandler(this);
+ ipHandler = new IpHandler(this);
+ routingRulePopulator = new RoutingRulePopulator(this);
+ defaultRoutingHandler = new DefaultRoutingHandler(this);
+
packetService.addProcessor(processor, PacketProcessor.ADVISOR_MAX + 2);
linkService.addListener(new InternalLinkListener());
groupService.addListener(new InternalGroupListener());
@@ -271,6 +283,7 @@
private class InternalEventHandler implements Runnable {
+ @Override
public void run() {
numOfHandlerExecution++;
while (!eventQueue.isEmpty()) {
@@ -326,7 +339,11 @@
log.debug("A new device with ID {} was added", device.id());
defaultRoutingHandler.populateTtpRules(device.id());
DefaultGroupHandler dgh = DefaultGroupHandler.createGroupHandler(
- device.id(), appId, new DeviceConfiguration(), linkService, groupService);
+ device.id(),
+ appId,
+ deviceConfiguration,
+ linkService,
+ groupService);
dgh.createGroups();
groupHandlerMap.put(device.id(), dgh);
}
diff --git a/src/main/java/org/onosproject/segmentrouting/config/NetworkConfig.java b/src/main/java/org/onosproject/segmentrouting/config/NetworkConfig.java
new file mode 100644
index 0000000..6ae0779
--- /dev/null
+++ b/src/main/java/org/onosproject/segmentrouting/config/NetworkConfig.java
@@ -0,0 +1,336 @@
+package org.onosproject.segmentrouting.config;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.onosproject.net.DeviceId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Public class corresponding to JSON described data model. Defines the network
+ * configuration at startup.
+ */
+public class NetworkConfig {
+ protected static final Logger log = LoggerFactory.getLogger(NetworkConfig.class);
+
+ @SuppressWarnings("unused")
+ private String comment;
+
+ private Boolean restrictSwitches;
+ private Boolean restrictLinks;
+ private List<SwitchConfig> switches;
+ private List<LinkConfig> links;
+
+ /**
+ * Default constructor.
+ */
+ public NetworkConfig() {
+ switches = new ArrayList<SwitchConfig>();
+ links = new ArrayList<LinkConfig>();
+ }
+
+ @JsonProperty("comment")
+ public void setComment(String c) {
+ log.trace("NetworkConfig: comment={}", c);
+ comment = c;
+ }
+
+ @JsonProperty("restrictSwitches")
+ public void setRestrictSwitches(boolean rs) {
+ log.trace("NetworkConfig: restrictSwitches={}", rs);
+ restrictSwitches = rs;
+ }
+
+ /**
+ * Returns default restrict configuration for switches.
+ *
+ * @return boolean
+ */
+ public Boolean getRestrictSwitches() {
+ return restrictSwitches;
+ }
+
+ @JsonProperty("restrictLinks")
+ public void setRestrictLinks(boolean rl) {
+ log.trace("NetworkConfig: restrictLinks={}", rl);
+ restrictLinks = rl;
+ }
+
+ /**
+ * Returns default restrict configuration for links.
+ *
+ * @return boolean
+ */
+ public Boolean getRestrictLinks() {
+ return restrictLinks;
+ }
+
+ /**
+ * Returns configuration for switches.
+ *
+ * @return list of switch configuration
+ */
+ public List<SwitchConfig> getSwitchConfig() {
+ return switches;
+ }
+
+ @JsonProperty("switchConfig")
+ public void setSwitchConfig(List<SwitchConfig> switches2) {
+ log.trace("NetworkConfig: switchConfig={}", switches2);
+ this.switches = switches2;
+ }
+
+ /**
+ * Java class corresponding to JSON described switch
+ * configuration data model.
+ */
+ public static class SwitchConfig {
+ protected String nodeDpid;
+ protected String name;
+ protected String type;
+ protected boolean allowed;
+ protected double latitude;
+ protected double longitude;
+ protected Map<String, JsonNode> params;
+ protected Map<String, String> publishAttributes;
+ protected DeviceId dpid;
+
+ /**
+ * Returns the configured "name" of a switch.
+ *
+ * @return string
+ */
+ public String getName() {
+ return name;
+ }
+
+ @JsonProperty("name")
+ public void setName(String name) {
+ log.trace("SwitchConfig: name={}", name);
+ this.name = name;
+ }
+
+ /**
+ * Returns the data plane identifier of a switch.
+ *
+ * @return ONOS device identifier
+ */
+ public DeviceId getDpid() {
+ return dpid;
+ }
+
+ public void setDpid(DeviceId dpid) {
+ this.dpid = dpid;
+ this.nodeDpid = dpid.toString();
+ }
+
+ /**
+ * Returns the data plane identifier of a switch.
+ *
+ * @return string
+ */
+ public String getNodeDpid() {
+ return nodeDpid;
+ }
+
+ // mapper sets both DeviceId and string fields for dpid
+ @JsonProperty("nodeDpid")
+ public void setNodeDpid(String nodeDpid) {
+ log.trace("SwitchConfig: nodeDpid={}", nodeDpid);
+ this.nodeDpid = nodeDpid;
+ this.dpid = DeviceId.deviceId(nodeDpid);
+ }
+
+ /**
+ * Returns the type of a switch.
+ *
+ * @return string
+ */
+ public String getType() {
+ return type;
+ }
+
+ @JsonProperty("type")
+ public void setType(String type) {
+ log.trace("SwitchConfig: type={}", type);
+ this.type = type;
+ }
+
+ /**
+ * Returns the latitude of a switch.
+ *
+ * @return double
+ */
+ public double getLatitude() {
+ return latitude;
+ }
+
+ @JsonProperty("latitude")
+ public void setLatitude(double latitude) {
+ log.trace("SwitchConfig: latitude={}", latitude);
+ this.latitude = latitude;
+ }
+
+ /**
+ * Returns the longitude of a switch.
+ *
+ * @return double
+ */
+ public double getLongitude() {
+ return longitude;
+ }
+
+ @JsonProperty("longitude")
+ public void setLongitude(double longitude) {
+ log.trace("SwitchConfig: longitude={}", longitude);
+ this.longitude = longitude;
+ }
+
+ /**
+ * Returns the allowed flag for a switch.
+ *
+ * @return boolean
+ */
+ public boolean isAllowed() {
+ return allowed;
+ }
+
+ @JsonProperty("allowed")
+ public void setAllowed(boolean allowed) {
+ this.allowed = allowed;
+ }
+
+ /**
+ * Returns the additional configured parameters of a switch.
+ *
+ * @return key value map
+ */
+ public Map<String, JsonNode> getParams() {
+ return params;
+ }
+
+ @JsonProperty("params")
+ public void setParams(Map<String, JsonNode> params) {
+ this.params = params;
+ }
+
+ /**
+ * Reserved for future use.
+ *
+ * @return key value map
+ */
+ public Map<String, String> getPublishAttributes() {
+ return publishAttributes;
+ }
+
+ @JsonProperty("publishAttributes")
+ public void setPublishAttributes(Map<String, String> publishAttributes) {
+ this.publishAttributes = publishAttributes;
+ }
+
+ }
+
+ @JsonProperty("linkConfig")
+ public void setLinkConfig(List<LinkConfig> links2) {
+ this.links = links2;
+ }
+
+ /**
+ * Reserved for future use.
+ *
+ * @return list of configured link configuration
+ */
+ public List<LinkConfig> getLinkConfig() {
+ return links;
+ }
+
+ /**
+ * Reserved for future use.
+ */
+ public static class LinkConfig {
+ protected String type;
+ protected Boolean allowed;
+ protected DeviceId dpid1;
+ protected DeviceId dpid2;
+ protected String nodeDpid1;
+ protected String nodeDpid2;
+ protected Map<String, JsonNode> params;
+ protected Map<String, String> publishAttributes;
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public Boolean isAllowed() {
+ return allowed;
+ }
+
+ public void setAllowed(Boolean allowed) {
+ this.allowed = allowed;
+ }
+
+ public String getNodeDpid1() {
+ return nodeDpid1;
+ }
+
+ // mapper sets both long and string fields for dpid
+ public void setNodeDpid1(String nodeDpid1) {
+ this.nodeDpid1 = nodeDpid1;
+ this.dpid1 = DeviceId.deviceId(nodeDpid1);
+ }
+
+ public String getNodeDpid2() {
+ return nodeDpid2;
+ }
+
+ // mapper sets both long and string fields for dpid
+ public void setNodeDpid2(String nodeDpid2) {
+ this.nodeDpid2 = nodeDpid2;
+ this.dpid2 = DeviceId.deviceId(nodeDpid2);
+ }
+
+ public DeviceId getDpid1() {
+ return dpid1;
+ }
+
+ public void setDpid1(DeviceId dpid1) {
+ this.dpid1 = dpid1;
+ this.nodeDpid1 = dpid1.toString();
+ }
+
+ public DeviceId getDpid2() {
+ return dpid2;
+ }
+
+ public void setDpid2(DeviceId dpid2) {
+ this.dpid2 = dpid2;
+ this.nodeDpid2 = dpid2.toString();
+ }
+
+ public Map<String, JsonNode> getParams() {
+ return params;
+ }
+
+ public void setParams(Map<String, JsonNode> params) {
+ this.params = params;
+ }
+
+ public Map<String, String> getPublishAttributes() {
+ return publishAttributes;
+ }
+
+ public void setPublishAttributes(Map<String, String> publishAttributes) {
+ this.publishAttributes = publishAttributes;
+ }
+ }
+}
+
diff --git a/src/main/java/org/onosproject/segmentrouting/config/NetworkConfigException.java b/src/main/java/org/onosproject/segmentrouting/config/NetworkConfigException.java
new file mode 100644
index 0000000..9194221
--- /dev/null
+++ b/src/main/java/org/onosproject/segmentrouting/config/NetworkConfigException.java
@@ -0,0 +1,200 @@
+package org.onosproject.segmentrouting.config;
+
+import org.onosproject.net.DeviceId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * NetworkConfigExceptions specifies a set of unchecked runtime exceptions that
+ * can be thrown by the {@link NetworkConfigManager}. It indicates errors that
+ * must be fixed in the config file before controller execution can proceed.
+ */
+public class NetworkConfigException extends RuntimeException {
+
+ private static final long serialVersionUID = 4959684709803000652L;
+ protected static final Logger log = LoggerFactory
+ .getLogger(NetworkConfigException.class);
+
+ /**
+ * Exception for duplicate device identifier configuration.
+ */
+ public static class DuplicateDpid extends RuntimeException {
+ private static final long serialVersionUID = 5491113234592145335L;
+
+ public DuplicateDpid(DeviceId dpid) {
+ super();
+ log.error("Duplicate dpid found in switch-config Dpid:{}",
+ dpid);
+ }
+ }
+
+ /**
+ * Exception for duplicate device name configuration.
+ */
+ public static class DuplicateName extends RuntimeException {
+ private static final long serialVersionUID = -4090171438031376129L;
+
+ public DuplicateName(String name) {
+ super();
+ log.error("Duplicate name found in switch-config name:{}", name);
+ }
+ }
+
+ /**
+ * Exception for unspecified device identifier for a switch.
+ */
+ public static class DpidNotSpecified extends RuntimeException {
+ private static final long serialVersionUID = -8494418855597117254L;
+
+ public DpidNotSpecified(String name) {
+ super();
+ log.error("Dpid not specified for switch-config name:{}", name);
+ }
+ }
+
+ /**
+ * Exception for unspecified device name for a switch.
+ */
+ public static class NameNotSpecified extends RuntimeException {
+ private static final long serialVersionUID = -3518881744110422891L;
+
+ public NameNotSpecified(DeviceId dpid) {
+ super();
+ log.error("Name not specified for switch-config dpid:{}",
+ dpid);
+ }
+ }
+
+ /**
+ * Exception for unspecified device type for a switch.
+ */
+ public static class SwitchTypeNotSpecified extends RuntimeException {
+ private static final long serialVersionUID = 2527453336226053753L;
+
+ public SwitchTypeNotSpecified(DeviceId dpid) {
+ super();
+ log.error("Switch type not specified for switch-config dpid:{}",
+ dpid);
+ }
+ }
+
+ /**
+ * Exception for unknown device type configured for a switch.
+ */
+ public static class UnknownSwitchType extends RuntimeException {
+ private static final long serialVersionUID = 7758418165512249170L;
+
+ public UnknownSwitchType(String type, String name) {
+ super();
+ log.error("Unknown switch type {} for switch name:{}", type, name);
+ }
+ }
+
+ /**
+ * Exception for missing required parameter configuration for a switch.
+ */
+ public static class ParamsNotSpecified extends RuntimeException {
+ private static final long serialVersionUID = 6247582323691265513L;
+
+ public ParamsNotSpecified(String name) {
+ super();
+ log.error("Params required - not specified for switch:{}", name);
+ }
+ }
+
+ /**
+ * Reserved for future use.
+ */
+ public static class LinkTypeNotSpecified extends RuntimeException {
+ private static final long serialVersionUID = -2089470389588542215L;
+
+ public LinkTypeNotSpecified(String dpid1, String dpid2) {
+ super();
+ log.error("Link type not specified for link-config between "
+ + "dpid1:{} and dpid2:{}", dpid1, dpid2);
+ }
+ }
+
+ /**
+ * Reserved for future use.
+ */
+ public static class LinkDpidNotSpecified extends RuntimeException {
+ private static final long serialVersionUID = -5701825916378616004L;
+
+ public LinkDpidNotSpecified(String dpid1, String dpid2) {
+ super();
+ if (dpid1 == null) {
+ log.error("nodeDpid1 not specified for link-config ");
+ }
+ if (dpid2 == null) {
+ log.error("nodeDpid2 not specified for link-config ");
+ }
+ }
+ }
+
+ /**
+ * Reserved for future use.
+ */
+ public static class LinkForUnknownSwitchConfig extends RuntimeException {
+ private static final long serialVersionUID = -2910458439881964094L;
+
+ public LinkForUnknownSwitchConfig(String dpid) {
+ super();
+ log.error("Link configuration was specified for a switch-dpid {} "
+ + "that has not been configured", dpid);
+ }
+ }
+
+ /**
+ * Reserved for future use.
+ */
+ public static class UnknownLinkType extends RuntimeException {
+ private static final long serialVersionUID = -5505376193106542305L;
+
+ public UnknownLinkType(String linktype, String dpid1, String dpid2) {
+ super();
+ log.error("unknown link type {} for links between dpid1:{} "
+ + "and dpid2:{}", linktype, dpid1, dpid2);
+ }
+ }
+
+ /**
+ * Exception for generic configuration errors.
+ */
+ public static class ErrorConfig extends RuntimeException {
+ private static final long serialVersionUID = -2827406314700193147L;
+
+ public ErrorConfig(String errorMsg) {
+ super();
+ log.error(errorMsg);
+ }
+
+ }
+
+ /**
+ * Reserved for future use.
+ */
+ public static class SwitchDpidNotConverted extends RuntimeException {
+ private static final long serialVersionUID = 5640347104590170426L;
+
+ public SwitchDpidNotConverted(String name) {
+ super();
+ log.error("Switch dpid specified as a HexString {} does not match "
+ + "with long value", name);
+ }
+ }
+
+ /**
+ * Reserved for future use.
+ */
+ public static class LinkDpidNotConverted extends RuntimeException {
+ private static final long serialVersionUID = 2397245646094080774L;
+
+ public LinkDpidNotConverted(String dpid1, String dpid2) {
+ log.error("Dpids expressed as HexStrings for links between dpid1:{} "
+ + "and dpid2:{} do not match with long values", dpid1, dpid2);
+ }
+ }
+
+}
+
diff --git a/src/main/java/org/onosproject/segmentrouting/config/NetworkConfigManager.java b/src/main/java/org/onosproject/segmentrouting/config/NetworkConfigManager.java
new file mode 100644
index 0000000..44e4f1c
--- /dev/null
+++ b/src/main/java/org/onosproject/segmentrouting/config/NetworkConfigManager.java
@@ -0,0 +1,323 @@
+package org.onosproject.segmentrouting.config;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.segmentrouting.config.NetworkConfig.LinkConfig;
+import org.onosproject.segmentrouting.config.NetworkConfig.SwitchConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * NetworkConfigManager manages all network configuration for switches, links
+ * and any other state that needs to be configured for correct network
+ * operation.
+ *
+ */
+public class NetworkConfigManager implements NetworkConfigService {
+ protected static final Logger log = LoggerFactory
+ .getLogger(NetworkConfigManager.class);
+ private static final String CONFIG_DIR = "../config";
+ private static final String DEFAULT_CONFIG_FILE = "segmentrouting.conf";
+ private final String configFileName = DEFAULT_CONFIG_FILE;
+ /**
+ * JSON Config file needs to use one of the following types for defining the
+ * kind of switch or link it wishes to configure.
+ */
+ public static final String SEGMENT_ROUTER = "Router_SR";
+
+ public static final String PKT_LINK = "pktLink";
+
+ NetworkConfig networkConfig;
+ private ConcurrentMap<DeviceId, SwitchConfig> configuredSwitches;
+ private ConcurrentMap<Link, LinkConfig> configuredLinks;
+ private Map<String, DeviceId> nameToDpid;
+
+ @Override
+ public SwitchConfigStatus checkSwitchConfig(DeviceId dpid) {
+ SwitchConfig swc = configuredSwitches.get(dpid);
+ if (networkConfig.getRestrictSwitches()) {
+ // default deny behavior
+ if (swc == null) {
+ // switch is not configured - we deny this switch
+ return new SwitchConfigStatus(NetworkConfigState.DENY, null,
+ "Switch not configured, in network denying switches by default.");
+ }
+ if (swc.isAllowed()) {
+ // switch is allowed in config, return configured attributes
+ return new SwitchConfigStatus(NetworkConfigState.ACCEPT_ADD, swc);
+ } else {
+ // switch has been configured off (administratively down)
+ return new SwitchConfigStatus(NetworkConfigState.DENY, null,
+ "Switch configured down (allowed=false).");
+ }
+ } else {
+ // default allow behavior
+ if (swc == null) {
+ // no config to add
+ return new SwitchConfigStatus(NetworkConfigState.ACCEPT, null);
+ }
+ if (swc.isAllowed()) {
+ // switch is allowed in config, return configured attributes
+ return new SwitchConfigStatus(NetworkConfigState.ACCEPT_ADD, swc);
+ } else {
+ // switch has been configured off (administratively down)
+ return new SwitchConfigStatus(NetworkConfigState.DENY, null,
+ "Switch configured down (allowed=false).");
+ }
+ }
+
+ }
+
+ @Override
+ public LinkConfigStatus checkLinkConfig(Link linkTuple) {
+ LinkConfig lkc = getConfiguredLink(linkTuple);
+ // links are always disallowed if any one of the nodes that make up the
+ // link are disallowed
+ DeviceId linkNode1 = linkTuple.src().deviceId();
+ SwitchConfigStatus scs1 = checkSwitchConfig(linkNode1);
+ if (scs1.getConfigState() == NetworkConfigState.DENY) {
+ return new LinkConfigStatus(NetworkConfigState.DENY, null,
+ "Link-node: " + linkNode1 + " denied by config: " + scs1.getMsg());
+ }
+ DeviceId linkNode2 = linkTuple.dst().deviceId();
+ SwitchConfigStatus scs2 = checkSwitchConfig(linkNode2);
+ if (scs2.getConfigState() == NetworkConfigState.DENY) {
+ return new LinkConfigStatus(NetworkConfigState.DENY, null,
+ "Link-node: " + linkNode2 + " denied by config: " + scs2.getMsg());
+ }
+ if (networkConfig.getRestrictLinks()) {
+ // default deny behavior
+ if (lkc == null) {
+ // link is not configured - we deny this link
+ return new LinkConfigStatus(NetworkConfigState.DENY, null,
+ "Link not configured, in network denying links by default.");
+ }
+ if (lkc.isAllowed()) {
+ // link is allowed in config, return configured attributes
+ return new LinkConfigStatus(NetworkConfigState.ACCEPT_ADD, lkc);
+ } else {
+ // link has been configured off (administratively down)
+ return new LinkConfigStatus(NetworkConfigState.DENY, null,
+ "Link configured down (allowed=false).");
+ }
+ } else {
+ // default allow behavior
+ if (lkc == null) {
+ // no config to add
+ return new LinkConfigStatus(NetworkConfigState.ACCEPT, null);
+ }
+ if (lkc.isAllowed()) {
+ // link is allowed in config, return configured attributes
+ return new LinkConfigStatus(NetworkConfigState.ACCEPT_ADD, lkc);
+ } else {
+ // link has been configured off (administratively down)
+ return new LinkConfigStatus(NetworkConfigState.DENY, null,
+ "Link configured down (allowed=false).");
+ }
+ }
+
+ }
+
+ @Override
+ public List<SwitchConfig> getConfiguredAllowedSwitches() {
+ List<SwitchConfig> allowed = new ArrayList<SwitchConfig>();
+ for (SwitchConfig swc : configuredSwitches.values()) {
+ if (swc.isAllowed()) {
+ allowed.add(swc);
+ }
+ }
+ return allowed;
+ }
+
+ @Override
+ public List<LinkConfig> getConfiguredAllowedLinks() {
+ List<LinkConfig> allowed = new ArrayList<LinkConfig>();
+ for (LinkConfig lkc : configuredLinks.values()) {
+ if (lkc.isAllowed()) {
+ allowed.add(lkc);
+ }
+ }
+ return allowed;
+ }
+
+ @Override
+ public DeviceId getDpidForName(String name) {
+ if (nameToDpid.get(name) != null) {
+ return nameToDpid.get(name);
+ }
+ return null;
+ }
+
+ // **************
+ // Private methods
+ // **************
+
+ private void loadNetworkConfig() {
+ File configFile = new File(CONFIG_DIR, configFileName);
+ ObjectMapper mapper = new ObjectMapper();
+ networkConfig = new NetworkConfig();
+
+ try {
+ networkConfig = mapper.readValue(configFile,
+ NetworkConfig.class);
+ } catch (JsonParseException e) {
+ String err = String.format("JsonParseException while loading network "
+ + "config from file: %s: %s", configFileName,
+ e.getMessage());
+ throw new NetworkConfigException.ErrorConfig(err);
+ } catch (JsonMappingException e) {
+ String err = String.format(
+ "JsonMappingException while loading network config "
+ + "from file: %s: %s",
+ configFileName,
+ e.getMessage());
+ throw new NetworkConfigException.ErrorConfig(err);
+ } catch (IOException e) {
+ String err = String.format("IOException while loading network config "
+ + "from file: %s %s", configFileName, e.getMessage());
+ throw new NetworkConfigException.ErrorConfig(err);
+ }
+
+ log.info("Network config specifies: {} switches and {} links",
+ (networkConfig.getRestrictSwitches())
+ ? networkConfig.getSwitchConfig().size() : "default allow",
+ (networkConfig.getRestrictLinks())
+ ? networkConfig.getLinkConfig().size() : "default allow");
+ }
+
+ private void parseNetworkConfig() {
+ List<SwitchConfig> swConfList = networkConfig.getSwitchConfig();
+ List<LinkConfig> lkConfList = networkConfig.getLinkConfig();
+ validateSwitchConfig(swConfList);
+ createTypeSpecificSwitchConfig(swConfList);
+ validateLinkConfig(lkConfList);
+ createTypeSpecificLinkConfig(lkConfList);
+ // TODO validate reachability matrix 'names' for configured dpids
+ }
+
+ private void createTypeSpecificSwitchConfig(List<SwitchConfig> swConfList) {
+ for (SwitchConfig swc : swConfList) {
+ nameToDpid.put(swc.getName(), swc.getDpid());
+ String swtype = swc.getType();
+ switch (swtype) {
+ case SEGMENT_ROUTER:
+ SwitchConfig sr = new SegmentRouterConfig(swc);
+ configuredSwitches.put(sr.getDpid(), sr);
+ break;
+ default:
+ throw new NetworkConfigException.UnknownSwitchType(swtype,
+ swc.getName());
+ }
+ }
+ }
+
+ private void createTypeSpecificLinkConfig(List<LinkConfig> lkConfList) {
+ for (LinkConfig lkc : lkConfList) {
+ String lktype = lkc.getType();
+ switch (lktype) {
+ case PKT_LINK:
+ PktLinkConfig pk = new PktLinkConfig(lkc);
+ for (Link lt : pk.getLinkTupleList()) {
+ configuredLinks.put(lt, pk);
+ }
+ break;
+ default:
+ throw new NetworkConfigException.UnknownLinkType(lktype,
+ lkc.getNodeDpid1(), lkc.getNodeDpid2());
+ }
+ }
+ }
+
+ private void validateSwitchConfig(List<SwitchConfig> swConfList) {
+ Set<DeviceId> swDpids = new HashSet<DeviceId>();
+ Set<String> swNames = new HashSet<String>();
+ for (SwitchConfig swc : swConfList) {
+ if (swc.getNodeDpid() == null || swc.getDpid() == null) {
+ throw new NetworkConfigException.DpidNotSpecified(swc.getName());
+ }
+ // ensure both String and DeviceId values of dpid are set
+ if (!swc.getDpid().equals(DeviceId.deviceId(swc.getNodeDpid()))) {
+ throw new NetworkConfigException.SwitchDpidNotConverted(
+ swc.getName());
+ }
+ if (swc.getName() == null) {
+ throw new NetworkConfigException.NameNotSpecified(swc.getDpid());
+ }
+ if (swc.getType() == null) {
+ throw new NetworkConfigException.SwitchTypeNotSpecified(
+ swc.getDpid());
+ }
+ if (!swDpids.add(swc.getDpid())) {
+ throw new NetworkConfigException.DuplicateDpid(swc.getDpid());
+ }
+ if (!swNames.add(swc.getName())) {
+ throw new NetworkConfigException.DuplicateName(swc.getName());
+ }
+ // TODO Add more validations
+ }
+ }
+
+ private void validateLinkConfig(List<LinkConfig> lkConfList) {
+ for (LinkConfig lkc : lkConfList) {
+ if (lkc.getNodeDpid1() == null || lkc.getNodeDpid2() == null) {
+ throw new NetworkConfigException.LinkDpidNotSpecified(
+ lkc.getNodeDpid1(), lkc.getNodeDpid2());
+ }
+ // ensure both String and Long values are set
+ if (!lkc.getDpid1().equals(DeviceId.deviceId(lkc.getNodeDpid1())) ||
+ !lkc.getDpid2().equals(DeviceId.deviceId(lkc.getNodeDpid2()))) {
+ throw new NetworkConfigException.LinkDpidNotConverted(
+ lkc.getNodeDpid1(), lkc.getNodeDpid2());
+ }
+ if (lkc.getType() == null) {
+ throw new NetworkConfigException.LinkTypeNotSpecified(
+ lkc.getNodeDpid1(), lkc.getNodeDpid2());
+ }
+ if (configuredSwitches.get(lkc.getDpid1()) == null) {
+ throw new NetworkConfigException.LinkForUnknownSwitchConfig(
+ lkc.getNodeDpid1());
+ }
+ if (configuredSwitches.get(lkc.getDpid2()) == null) {
+ throw new NetworkConfigException.LinkForUnknownSwitchConfig(
+ lkc.getNodeDpid2());
+ }
+ // TODO add more validations
+ }
+
+ }
+
+ private LinkConfig getConfiguredLink(Link linkTuple) {
+ LinkConfig lkc = null;
+ // first try the unidirectional link with the ports assigned
+ lkc = configuredLinks.get(linkTuple);
+ return lkc;
+ }
+
+
+ /**
+ * Initializes the network configuration manager module by
+ * loading and parsing the network configuration file.
+ */
+ public void init() {
+ loadNetworkConfig();
+ configuredSwitches = new ConcurrentHashMap<DeviceId, SwitchConfig>();
+ configuredLinks = new ConcurrentHashMap<Link, LinkConfig>();
+ nameToDpid = new HashMap<String, DeviceId>();
+ parseNetworkConfig();
+ }
+}
diff --git a/src/main/java/org/onosproject/segmentrouting/config/NetworkConfigService.java b/src/main/java/org/onosproject/segmentrouting/config/NetworkConfigService.java
new file mode 100644
index 0000000..722d50c
--- /dev/null
+++ b/src/main/java/org/onosproject/segmentrouting/config/NetworkConfigService.java
@@ -0,0 +1,253 @@
+package org.onosproject.segmentrouting.config;
+
+import java.util.List;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.segmentrouting.config.NetworkConfig.LinkConfig;
+import org.onosproject.segmentrouting.config.NetworkConfig.SwitchConfig;
+
+/**
+ * Exposes methods to retrieve network configuration.
+ *
+ * TODO: currently only startup-configuration is exposed and such configuration
+ * cannot be changed at runtime. Need to add runtime support for changes to
+ * configuration (via REST/CLI) in future releases.
+ *
+ * TODO: return immutable objects or defensive copies of network config so that
+ * users of this API do not inadvertently or maliciously change network config.
+ */
+public interface NetworkConfigService {
+
+ /**
+ * Suggests the action to be taken by the caller given the configuration
+ * associated with the queried network-object (eg. switch, link etc.).
+ */
+ public enum NetworkConfigState {
+ /**
+ * Associated network object has been configured to not be allowed in
+ * the network.
+ */
+ DENY,
+
+ /**
+ * Associated network object has been configured to be allowed in the
+ * network.
+ */
+ ACCEPT,
+
+ /**
+ * Associated network object has been configured to be allowed in the
+ * network. In addition, there are configured parameters that should be
+ * added to the object.
+ */
+ ACCEPT_ADD,
+ }
+
+ /**
+ * Returns the configuration outcome (accept, deny etc.), and any configured
+ * parameters to the caller, in response to a query for the configuration
+ * associated with a switch.
+ */
+ public class SwitchConfigStatus {
+ private NetworkConfigState configState;
+ private SwitchConfig switchConfig;
+ private String msg;
+
+ SwitchConfigStatus(NetworkConfigState configState,
+ SwitchConfig switchConfig, String msg) {
+ this.configState = configState;
+ this.switchConfig = switchConfig;
+ this.msg = msg;
+ }
+
+ SwitchConfigStatus(NetworkConfigState configState,
+ SwitchConfig switchConfig) {
+ this.configState = configState;
+ this.switchConfig = switchConfig;
+ this.msg = "";
+ }
+
+ /**
+ * Returns the configuration state for the switch.
+ *
+ * @return non-null NetworkConfigState
+ */
+ public NetworkConfigState getConfigState() {
+ return configState;
+ }
+
+ /**
+ * Returns the switch configuration, which may be null if no
+ * configuration exists, or if the configuration state disallows the
+ * switch.
+ *
+ * @return SwitchConfig, the switch configuration, or null
+ */
+ public SwitchConfig getSwitchConfig() {
+ return switchConfig;
+ }
+
+ /**
+ * User readable string typically used to specify the reason why a
+ * switch is being disallowed.
+ *
+ * @return A non-null but possibly empty String
+ */
+ public String getMsg() {
+ return msg;
+ }
+
+ }
+
+ /**
+ * Reserved for future use.
+ *
+ * Returns the configuration outcome (accept, deny etc.), and any configured
+ * parameters to the caller, in response to a query for the configuration
+ * associated with a link.
+ */
+ public class LinkConfigStatus {
+ private NetworkConfigState configState;
+ private LinkConfig linkConfig;
+ private String msg;
+
+ LinkConfigStatus(NetworkConfigState configState,
+ LinkConfig linkConfig, String msg) {
+ this.configState = configState;
+ this.linkConfig = linkConfig;
+ this.msg = msg;
+ }
+
+ LinkConfigStatus(NetworkConfigState configState,
+ LinkConfig linkConfig) {
+ this.configState = configState;
+ this.linkConfig = linkConfig;
+ this.msg = "";
+ }
+
+ /**
+ * Returns the configuration state for the link.
+ *
+ * @return non-null NetworkConfigState
+ */
+ public NetworkConfigState getConfigState() {
+ return configState;
+ }
+
+ /**
+ * Returns the link configuration, which may be null if no configuration
+ * exists, or if the configuration state disallows the link.
+ *
+ * @return SwitchConfig, the switch configuration, or null
+ */
+ public LinkConfig getLinkConfig() {
+ return linkConfig;
+ }
+
+ /**
+ * User readable string typically used to specify the reason why a link
+ * is being disallowed.
+ *
+ * @return msg A non-null but possibly empty String
+ */
+ public String getMsg() {
+ return msg;
+ }
+
+ }
+
+ /**
+ * Checks the switch configuration (if any) associated with the 'dpid'.
+ * Determines if the switch should be allowed or denied according to
+ * configuration rules.
+ *
+ * The method always returns a non-null SwitchConfigStatus. The enclosed
+ * ConfigState contains the result of the check. The enclosed SwitchConfig
+ * may or may not be null, depending on the outcome of the check.
+ *
+ * @param dpid device id of the switch to be queried
+ * @return SwitchConfigStatus with outcome of check and associated config.
+ */
+ public SwitchConfigStatus checkSwitchConfig(DeviceId dpid);
+
+ /**
+ * Reserved for future use.
+ *
+ * Checks the link configuration (if any) associated with the 'link'.
+ * Determines if the link should be allowed or denied according to
+ * configuration rules. Note that the 'link' is a unidirectional link which
+ * checked against configuration that is typically defined for a
+ * bidirectional link. The caller may make a second call if it wishes to
+ * check the 'reverse' direction.
+ *
+ * Also note that the configuration may not specify ports for a given
+ * bidirectional link. In such cases, the configuration applies to all links
+ * between the two switches. This method will check the given 'link' against
+ * such configuration.
+
+ * The method always returns a non-null LinkConfigStatus. The enclosed
+ * ConfigState contains the result of the check. The enclosed LinkConfig may
+ * or may not be null, depending on the outcome of the check.
+ *
+ * @param linkTuple unidirectional link to be queried
+ * @return LinkConfigStatus with outcome of check and associated config.
+ */
+ public LinkConfigStatus checkLinkConfig(Link linkTuple);
+
+ /**
+ * Retrieves a list of switches that have been configured, and have been
+ * determined to be 'allowed' in the network, according to configuration
+ * rules.
+ *
+ * Note that it is possible that there are other switches that are allowed
+ * in the network that have NOT been configured. Such switches will not be a
+ * part of the returned list.
+ *
+ * Also note that it is possible that some switches will not be discovered
+ * and the only way the controller can know about these switches is via
+ * configuration. Such switches will be included in this list. It is up to
+ * the caller to determine which SwitchConfig applies to non-discovered
+ * switches.
+ *
+ * @return a non-null List of SwitchConfig which may be empty
+ */
+ public List<SwitchConfig> getConfiguredAllowedSwitches();
+
+ /**
+ * Reserved for future use.
+ *
+ * Retrieves a list of links that have been configured, and have been
+ * determined to be 'allowed' in the network, according to configuration
+ * rules.
+ *
+ * Note that it is possible that there are other links that are allowed in
+ * the network that have NOT been configured. Such links will not be a part
+ * of the returned list.
+ *
+ * Also note that it is possible that some links will not be discovered and
+ * the only way the controller can know about these links is via
+ * configuration. Such links will be included in this list. It is up to the
+ * caller to determine which LinkConfig applies to non-discovered links.
+ *
+ * In addition, note that the LinkConfig applies to the configured
+ * bi-directional link, which may or may not have declared ports. The
+ * associated unidirectional LinkTuple can be retrieved from the
+ * getLinkTupleList() method in the LinkConfig object.
+ *
+ * @return a non-null List of LinkConfig which may be empty
+ */
+ public List<LinkConfig> getConfiguredAllowedLinks();
+
+ /**
+ * Retrieves the Dpid associated with a 'name' for a configured switch
+ * object. This method does not check of the switches are 'allowed' by
+ * config.
+ *
+ * @param name device name
+ * @return the Dpid corresponding to a given 'name', or null if no
+ * configured switch was found for the given 'name'.
+ */
+ public DeviceId getDpidForName(String name);
+
+}
diff --git a/src/main/java/org/onosproject/segmentrouting/config/PktLinkConfig.java b/src/main/java/org/onosproject/segmentrouting/config/PktLinkConfig.java
new file mode 100644
index 0000000..c66ac3c
--- /dev/null
+++ b/src/main/java/org/onosproject/segmentrouting/config/PktLinkConfig.java
@@ -0,0 +1,144 @@
+package org.onosproject.segmentrouting.config;
+
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.onosproject.net.Link;
+import org.onosproject.segmentrouting.config.NetworkConfig.LinkConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Reserved for future use.
+ * Configuration for a link between two packet-switches.
+ */
+public class PktLinkConfig extends LinkConfig {
+ protected static final Logger log = LoggerFactory
+ .getLogger(PktLinkConfig.class);
+ private int port1;
+ private int port2;
+ private String nodeName1;
+ private String nodeName2;
+ private List<Link> linkTupleList;
+
+ public PktLinkConfig(LinkConfig lkc) {
+ nodeDpid1 = lkc.getNodeDpid1();
+ nodeDpid2 = lkc.getNodeDpid2();
+ dpid1 = lkc.getDpid1();
+ dpid2 = lkc.getDpid2();
+ type = lkc.getType();
+ allowed = lkc.isAllowed();
+ params = lkc.getParams();
+ publishAttributes = new ConcurrentHashMap<String, String>();
+ parseParams();
+ validateParams();
+ setPublishAttributes();
+ }
+
+ // ********************
+ // Packet Link Configuration
+ // ********************
+
+ public int getPort1() {
+ return port1;
+ }
+
+ public void setPort1(int port1) {
+ this.port1 = port1;
+ }
+
+ public int getPort2() {
+ return port2;
+ }
+
+ public void setPort2(int port2) {
+ this.port2 = port2;
+ }
+
+ public String getNodeName1() {
+ return nodeName1;
+ }
+
+ public void setNodeName1(String nodeName1) {
+ this.nodeName1 = nodeName1;
+ }
+
+ public String getNodeName2() {
+ return nodeName2;
+ }
+
+ public void setNodeName2(String nodeName2) {
+ this.nodeName2 = nodeName2;
+ }
+
+ /**
+ * Returns the two unidirectional links corresponding to the packet-link
+ * configuration. It is possible that the ports in the LinkTuple have
+ * portnumber '0', implying that the configuration applies to all links
+ * between the two switches.
+ *
+ * @return a list of LinkTuple with exactly 2 unidirectional links
+ */
+ public List<Link> getLinkTupleList() {
+ return linkTupleList;
+ }
+
+ private void setPublishAttributes() {
+
+ }
+
+ private void parseParams() {
+ if (params == null) {
+ throw new PktLinkParamsNotSpecified(nodeDpid1, nodeDpid2);
+ }
+ Set<Entry<String, JsonNode>> m = params.entrySet();
+ for (Entry<String, JsonNode> e : m) {
+ String key = e.getKey();
+ JsonNode j = e.getValue();
+ if (key.equals("nodeName1")) {
+ setNodeName1(j.asText());
+ } else if (key.equals("nodeName2")) {
+ setNodeName2(j.asText());
+ } else if (key.equals("port1")) {
+ setPort1(j.asInt());
+ } else if (key.equals("port2")) {
+ setPort2(j.asInt());
+ } else {
+ throw new UnknownPktLinkConfig(key, nodeDpid1, nodeDpid2);
+ }
+ }
+ }
+
+ private void validateParams() {
+ // TODO - wrong-names, duplicate links,
+ // duplicate use of port, is switch-allowed for which link is allowed?
+ // valid port numbers
+ }
+
+ public static class PktLinkParamsNotSpecified extends RuntimeException {
+ private static final long serialVersionUID = 6247582323691265513L;
+
+ public PktLinkParamsNotSpecified(String dpidA, String dpidB) {
+ super();
+ log.error("Params required for packet link - not specified "
+ + "for link between switch1:{} and switch2:{}",
+ dpidA, dpidB);
+ }
+ }
+
+ public static class UnknownPktLinkConfig extends RuntimeException {
+ private static final long serialVersionUID = -5750132094884129179L;
+
+ public UnknownPktLinkConfig(String key, String dpidA, String dpidB) {
+ super();
+ log.error("Unknown packet-link config {} for link between"
+ + " dpid1: {} and dpid2: {}", key,
+ dpidA, dpidB);
+ }
+ }
+
+}
diff --git a/src/main/java/org/onosproject/segmentrouting/config/SegmentRouterConfig.java b/src/main/java/org/onosproject/segmentrouting/config/SegmentRouterConfig.java
new file mode 100644
index 0000000..4775c77
--- /dev/null
+++ b/src/main/java/org/onosproject/segmentrouting/config/SegmentRouterConfig.java
@@ -0,0 +1,430 @@
+package org.onosproject.segmentrouting.config;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.segmentrouting.config.NetworkConfig.SwitchConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Manages additional configuration for switches configured as Segment Routers.
+ */
+public class SegmentRouterConfig extends SwitchConfig {
+ protected static final Logger log = LoggerFactory
+ .getLogger(SegmentRouterConfig.class);
+ private String routerIp;
+ private String routerMac;
+ private int nodeSid;
+ private boolean isEdgeRouter;
+ private List<AdjacencySid> adjacencySids;
+ private List<Subnet> subnets;
+
+ public static final String ROUTER_IP = "routerIp";
+ public static final String ROUTER_MAC = "routerMac";
+ public static final String NODE_SID = "nodeSid";
+ public static final String ADJACENCY_SIDS = "adjacencySids";
+ public static final String SUBNETS = "subnets";
+ public static final String ISEDGE = "isEdgeRouter";
+ private static final int SRGB_MAX = 1000;
+
+ /**
+ * Parses and validates the additional configuration parameters applicable
+ * to segment routers.
+ *
+ * @param swc switch configuration
+ */
+ public SegmentRouterConfig(SwitchConfig swc) {
+ this.setName(swc.getName());
+ this.setDpid(swc.getDpid());
+ this.setType(swc.getType());
+ this.setLatitude(swc.getLatitude());
+ this.setLongitude(swc.getLongitude());
+ this.setParams(swc.getParams());
+ this.setAllowed(swc.isAllowed());
+ publishAttributes = new ConcurrentHashMap<String, String>();
+ adjacencySids = new ArrayList<AdjacencySid>();
+ subnets = new ArrayList<Subnet>();
+ parseParams();
+ validateParams();
+ setPublishAttributes();
+ }
+
+ /**
+ * Returns the configured segment router IP address.
+ *
+ * @return ip address in string format
+ */
+ public String getRouterIp() {
+ return routerIp;
+ }
+
+ public void setRouterIp(String routerIp) {
+ this.routerIp = routerIp;
+ }
+
+ /**
+ * Returns the configured segment router mac address.
+ *
+ * @return mac address in string format
+ */
+ public String getRouterMac() {
+ return routerMac;
+ }
+
+ public void setRouterMac(String routerMac) {
+ this.routerMac = routerMac;
+ }
+
+ /**
+ * Returns the configured sID for a segment router.
+ *
+ * @return segment identifier
+ */
+ public int getNodeSid() {
+ return nodeSid;
+ }
+
+ public void setNodeSid(int nodeSid) {
+ this.nodeSid = nodeSid;
+ }
+
+ /**
+ * Returns the flag that indicates the configured segment router
+ * is edge or backbone router.
+ *
+ * @return boolean
+ */
+ public boolean isEdgeRouter() {
+ return isEdgeRouter;
+ }
+
+ public void setIsEdgeRouter(boolean isEdge) {
+ this.isEdgeRouter = isEdge;
+ }
+
+ /**
+ * Class representing segment router adjacency identifier.
+ */
+ public static class AdjacencySid {
+ private int adjSid;
+ private List<Integer> ports;
+
+ public AdjacencySid(int adjSid, List<Integer> ports) {
+ this.ports = ports;
+ this.adjSid = adjSid;
+ }
+
+ /**
+ * Returns the list of ports part of a segment
+ * router adjacency identifier.
+ *
+ * @return list of integers
+ */
+ public List<Integer> getPorts() {
+ return ports;
+ }
+
+ public void setPorts(List<Integer> ports) {
+ this.ports = ports;
+ }
+
+ /**
+ * Returns the configured adjacency id of a segment router.
+ *
+ * @return integer
+ */
+ public int getAdjSid() {
+ return adjSid;
+ }
+
+ public void setAdjSid(int adjSid) {
+ this.adjSid = adjSid;
+ }
+ }
+
+ /**
+ * Returns the configured adjacent segment IDs for a segment router.
+ *
+ * @return list of adjacency identifier
+ */
+ public List<AdjacencySid> getAdjacencySids() {
+ return adjacencySids;
+ }
+
+ public void setAdjacencySids(List<AdjacencySid> adjacencySids) {
+ this.adjacencySids = adjacencySids;
+ }
+
+ /**
+ * Class representing a subnet attached to a segment router.
+ */
+ public static class Subnet {
+ private int portNo;
+ private String subnetIp;
+
+ public Subnet(int portNo, String subnetIp) {
+ this.portNo = portNo;
+ this.subnetIp = subnetIp;
+ }
+
+ /**
+ * Returns the port number of segment router on
+ * which subnet is attached.
+ *
+ * @return integer
+ */
+ public int getPortNo() {
+ return portNo;
+ }
+
+ public void setPortNo(int portNo) {
+ this.portNo = portNo;
+ }
+
+ /**
+ * Returns the configured subnet address.
+ *
+ * @return subnet ip address in string format
+ */
+ public String getSubnetIp() {
+ return subnetIp;
+ }
+
+ public void setSubnetIp(String subnetIp) {
+ this.subnetIp = subnetIp;
+ }
+ }
+
+ /**
+ * Returns the configured subnets for a segment router.
+ *
+ * @return list of subnets
+ */
+ public List<Subnet> getSubnets() {
+ return subnets;
+ }
+
+ public void setSubnets(List<Subnet> subnets) {
+ this.subnets = subnets;
+ }
+
+ // ********************
+ // Helper methods
+ // ********************
+
+ private void parseParams() {
+ if (params == null) {
+ throw new NetworkConfigException.ParamsNotSpecified(name);
+ }
+
+ Set<Entry<String, JsonNode>> m = params.entrySet();
+ for (Entry<String, JsonNode> e : m) {
+ String key = e.getKey();
+ JsonNode j = e.getValue();
+ if (key.equals("routerIp")) {
+ setRouterIp(j.asText());
+ } else if (key.equals("routerMac")) {
+ setRouterMac(j.asText());
+ } else if (key.equals("nodeSid")) {
+ setNodeSid(j.asInt());
+ } else if (key.equals("isEdgeRouter")) {
+ setIsEdgeRouter(j.asBoolean());
+ } else if (key.equals("adjacencySids") || key.equals("subnets")) {
+ getInnerParams(j, key);
+ } else {
+ throw new UnknownSegmentRouterConfig(key, dpid);
+ }
+ }
+ }
+
+ private void getInnerParams(JsonNode j, String innerParam) {
+ Iterator<JsonNode> innerList = j.elements();
+ while (innerList.hasNext()) {
+ Iterator<Entry<String, JsonNode>> f = innerList.next().fields();
+ int portNo = -1;
+ int adjSid = -1;
+ String subnetIp = null;
+ List<Integer> ports = null;
+ while (f.hasNext()) {
+ Entry<String, JsonNode> fe = f.next();
+ if (fe.getKey().equals("portNo")) {
+ portNo = fe.getValue().asInt();
+ } else if (fe.getKey().equals("adjSid")) {
+ adjSid = fe.getValue().asInt();
+ } else if (fe.getKey().equals("subnetIp")) {
+ subnetIp = fe.getValue().asText();
+ } else if (fe.getKey().equals("ports")) {
+ if (fe.getValue().isArray()) {
+ Iterator<JsonNode> i = fe.getValue().elements();
+ ports = new ArrayList<Integer>();
+ while (i.hasNext()) {
+ ports.add(i.next().asInt());
+ }
+ }
+ } else {
+ throw new UnknownSegmentRouterConfig(fe.getKey(), dpid);
+ }
+ }
+ if (innerParam.equals("adjacencySids")) {
+ AdjacencySid ads = new AdjacencySid(adjSid, ports);
+ adjacencySids.add(ads);
+ } else {
+ Subnet sip = new Subnet(portNo, subnetIp);
+ subnets.add(sip);
+ }
+ }
+ }
+
+ private void validateParams() {
+ if (routerIp == null) {
+ throw new IpNotSpecified(dpid);
+ }
+ if (routerMac == null) {
+ throw new MacNotSpecified(dpid);
+ }
+ if (isEdgeRouter && subnets.isEmpty()) {
+ throw new SubnetNotSpecifiedInEdgeRouter(dpid);
+ }
+ if (!isEdgeRouter && !subnets.isEmpty()) {
+ throw new SubnetSpecifiedInBackboneRouter(dpid);
+ }
+ if (nodeSid > SRGB_MAX) {
+ throw new NodeLabelNotInSRGB(nodeSid, dpid);
+ }
+ for (AdjacencySid as : adjacencySids) {
+ int label = as.getAdjSid();
+ List<Integer> plist = as.getPorts();
+ if (label <= SRGB_MAX) {
+ throw new AdjacencyLabelInSRGB(label, dpid);
+ }
+ if (plist.size() <= 1) {
+ throw new AdjacencyLabelNotEnoughPorts(label, dpid);
+ }
+ }
+
+
+ // TODO more validations
+ }
+
+ /**
+ * Setting publishAttributes implies that this is the configuration that
+ * will be added to Topology.Switch object before it is published on the
+ * channel to other controller instances.
+ */
+ private void setPublishAttributes() {
+ publishAttributes.put(ROUTER_IP, routerIp);
+ publishAttributes.put(ROUTER_MAC, routerMac);
+ publishAttributes.put(NODE_SID, String.valueOf(nodeSid));
+ publishAttributes.put(ISEDGE, String.valueOf(isEdgeRouter));
+ ObjectMapper mapper = new ObjectMapper();
+ try {
+ publishAttributes.put(ADJACENCY_SIDS,
+ mapper.writeValueAsString(adjacencySids));
+ publishAttributes.put(SUBNETS,
+ mapper.writeValueAsString(subnets));
+ } catch (JsonProcessingException e) {
+ log.error("Error while writing SR config: {}", e.getCause());
+ } catch (IOException e) {
+ log.error("Error while writing SR config: {}", e.getCause());
+ }
+ }
+
+ // ********************
+ // Exceptions
+ // ********************
+
+ public static class IpNotSpecified extends RuntimeException {
+ private static final long serialVersionUID = -3001502553646331686L;
+
+ public IpNotSpecified(DeviceId dpid) {
+ super();
+ log.error("Router IP address not specified for SR config dpid:{}",
+ dpid);
+ }
+ }
+
+ public static class MacNotSpecified extends RuntimeException {
+ private static final long serialVersionUID = -5850132094884129179L;
+
+ public MacNotSpecified(DeviceId dpid) {
+ super();
+ log.error("Router Mac address not specified for SR config dpid:{}",
+ dpid);
+ }
+ }
+
+ public static class UnknownSegmentRouterConfig extends RuntimeException {
+ private static final long serialVersionUID = -5750132094884129179L;
+
+ public UnknownSegmentRouterConfig(String key, DeviceId dpid) {
+ super();
+ log.error("Unknown Segment Router config {} in dpid: {}", key,
+ dpid);
+ }
+ }
+
+ public static class SubnetNotSpecifiedInEdgeRouter extends RuntimeException {
+ private static final long serialVersionUID = -5855458472668581268L;
+
+ public SubnetNotSpecifiedInEdgeRouter(DeviceId dpid) {
+ super();
+ log.error("Subnet was not specified for edge router in dpid: {}",
+ dpid);
+ }
+ }
+
+ public static class SubnetSpecifiedInBackboneRouter extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ public SubnetSpecifiedInBackboneRouter(DeviceId dpid) {
+ super();
+ log.error("Subnet was specified in backbone router in dpid: {}",
+ dpid);
+ }
+ }
+
+ public static class NodeLabelNotInSRGB extends RuntimeException {
+ private static final long serialVersionUID = -8482670903748519526L;
+
+ public NodeLabelNotInSRGB(int label, DeviceId dpid) {
+ super();
+ log.error("Node sif {} specified in not in global label-base "
+ + "in dpid: {}", label,
+ dpid);
+ }
+ }
+
+ public static class AdjacencyLabelInSRGB extends RuntimeException {
+ private static final long serialVersionUID = -8482670903748519526L;
+
+ public AdjacencyLabelInSRGB(int label, DeviceId dpid) {
+ super();
+ log.error("Adjaceny label {} specified from global label-base "
+ + "in dpid: {}", label,
+ dpid);
+ }
+ }
+
+ public static class AdjacencyLabelNotEnoughPorts extends RuntimeException {
+ private static final long serialVersionUID = -8482670903748519526L;
+
+ public AdjacencyLabelNotEnoughPorts(int label, DeviceId dpid) {
+ super();
+ log.error("Adjaceny label {} must be specified for at least 2 ports. "
+ + "Adjacency labels for single ports are auto-generated "
+ + "in dpid: {}", label,
+ dpid);
+ }
+ }
+}
diff --git a/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultEdgeGroupHandler.java b/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultEdgeGroupHandler.java
new file mode 100644
index 0000000..0cd5743
--- /dev/null
+++ b/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultEdgeGroupHandler.java
@@ -0,0 +1,182 @@
+/*
+ * 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 java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.onlab.packet.MplsLabel;
+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<DeviceId> neighbors = devicePortMap.keySet();
+ if (neighbors == null || neighbors.isEmpty()) {
+ return;
+ }
+
+ // Create all possible Neighbor sets from this router
+ Set<Set<DeviceId>> powerSet = getPowerSetOfNeighbors(neighbors);
+ log.trace("createGroupsAtEdgeRouter: The size of neighbor powerset "
+ + "for sw {} is {}", deviceId, powerSet.size());
+ Set<NeighborSet> nsSet = new HashSet<NeighborSet>();
+ for (Set<DeviceId> combo : powerSet) {
+ if (combo.isEmpty()) {
+ continue;
+ }
+ List<Integer> 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<NeighborSet> 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<NeighborSet> 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);
+ if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) {
+ tBuilder.pushMpls()
+ .setMpls(MplsLabel.
+ mplsLabel(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,
+ getGroupKey(ns),
+ updatedBuckets,
+ getGroupKey(ns),
+ appId);
+ }
+ }
+
+ @Override
+ protected Set<NeighborSet> computeImpactedNeighborsetForPortEvent(
+ DeviceId impactedNeighbor,
+ Set<DeviceId> updatedNeighbors) {
+ Set<Set<DeviceId>> powerSet = getPowerSetOfNeighbors(updatedNeighbors);
+
+ Set<DeviceId> tmp = new HashSet<DeviceId>();
+ tmp.addAll(updatedNeighbors);
+ tmp.remove(impactedNeighbor);
+ Set<Set<DeviceId>> tmpPowerSet = getPowerSetOfNeighbors(tmp);
+
+ // Compute the impacted neighbor sets
+ powerSet.removeAll(tmpPowerSet);
+
+ Set<NeighborSet> nsSet = new HashSet<NeighborSet>();
+ for (Set<DeviceId> combo : powerSet) {
+ if (combo.isEmpty()) {
+ continue;
+ }
+ List<Integer> 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/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java b/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java
new file mode 100644
index 0000000..0ac3728
--- /dev/null
+++ b/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));
+ }
+
+}
diff --git a/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultTransitGroupHandler.java b/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultTransitGroupHandler.java
new file mode 100644
index 0000000..e053622
--- /dev/null
+++ b/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultTransitGroupHandler.java
@@ -0,0 +1,186 @@
+/*
+ * 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 java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.onlab.packet.MplsLabel;
+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<DeviceId> 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<Set<DeviceId>> sets = getPowerSetOfNeighbors(neighbors);
+ sets = filterEdgeRouterOnlyPairings(sets);
+ log.debug("createGroupsAtTransitRouter: The size of neighbor powerset "
+ + "for sw {} is {}", deviceId, sets.size());
+ Set<NeighborSet> nsSet = new HashSet<NeighborSet>();
+ for (Set<DeviceId> 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<NeighborSet> 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<NeighborSet> 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);
+ if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) {
+ tBuilder.pushMpls()
+ .setMpls(MplsLabel.
+ mplsLabel(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,
+ getGroupKey(ns),
+ updatedBuckets,
+ getGroupKey(ns),
+ appId);
+ }
+ }
+
+ @Override
+ protected Set<NeighborSet> computeImpactedNeighborsetForPortEvent(
+ DeviceId impactedNeighbor,
+ Set<DeviceId> updatedNeighbors) {
+ Set<Set<DeviceId>> powerSet = getPowerSetOfNeighbors(updatedNeighbors);
+
+ Set<DeviceId> tmp = new HashSet<DeviceId>();
+ tmp.addAll(updatedNeighbors);
+ tmp.remove(impactedNeighbor);
+ Set<Set<DeviceId>> tmpPowerSet = getPowerSetOfNeighbors(tmp);
+
+ // Compute the impacted neighbor sets
+ powerSet.removeAll(tmpPowerSet);
+
+ powerSet = filterEdgeRouterOnlyPairings(powerSet);
+ Set<NeighborSet> nsSet = new HashSet<NeighborSet>();
+ for (Set<DeviceId> 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<Set<DeviceId>> filterEdgeRouterOnlyPairings(Set<Set<DeviceId>> sets) {
+ Set<Set<DeviceId>> fiteredSets = new HashSet<Set<DeviceId>>();
+ for (Set<DeviceId> 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/src/main/java/org/onosproject/segmentrouting/grouphandler/DeviceProperties.java b/src/main/java/org/onosproject/segmentrouting/grouphandler/DeviceProperties.java
new file mode 100644
index 0000000..816ca7b
--- /dev/null
+++ b/src/main/java/org/onosproject/segmentrouting/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.segmentrouting.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<Integer> getAllDeviceSegmentIds();
+}
diff --git a/src/main/java/org/onosproject/segmentrouting/grouphandler/GroupBucketIdentifier.java b/src/main/java/org/onosproject/segmentrouting/grouphandler/GroupBucketIdentifier.java
new file mode 100644
index 0000000..2ecb449
--- /dev/null
+++ b/src/main/java/org/onosproject/segmentrouting/grouphandler/GroupBucketIdentifier.java
@@ -0,0 +1,69 @@
+/*
+ * 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 org.onosproject.net.PortNumber;
+
+/**
+ * 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 PolicyGroupIdentifier 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,
+ PolicyGroupIdentifier 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 PolicyGroupIdentifier outGroup() {
+ return this.outGroup;
+ }
+}
+
diff --git a/src/main/java/org/onosproject/segmentrouting/grouphandler/NeighborSet.java b/src/main/java/org/onosproject/segmentrouting/grouphandler/NeighborSet.java
new file mode 100644
index 0000000..7ada322
--- /dev/null
+++ b/src/main/java/org/onosproject/segmentrouting/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.segmentrouting.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;
+
+/**
+ * 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 {
+ private final Set<DeviceId> neighbors;
+ private final int edgeLabel;
+ public static final int NO_EDGE_LABEL = -1;
+
+ /**
+ * 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<DeviceId> neighbors) {
+ checkNotNull(neighbors);
+ this.edgeLabel = NO_EDGE_LABEL;
+ this.neighbors = new HashSet<DeviceId>();
+ 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<DeviceId> neighbors, int edgeLabel) {
+ checkNotNull(neighbors);
+ this.edgeLabel = edgeLabel;
+ this.neighbors = new HashSet<DeviceId>();
+ this.neighbors.addAll(neighbors);
+ }
+
+ /**
+ * Default constructor for kryo serialization.
+ */
+ public NeighborSet() {
+ this.edgeLabel = NO_EDGE_LABEL;
+ this.neighbors = new HashSet<DeviceId>();
+ }
+
+ /**
+ * Gets the neighbors part of neighbor set.
+ *
+ * @return set of neighbor identifiers
+ */
+ public Set<DeviceId> 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/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupHandler.java b/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupHandler.java
new file mode 100644
index 0000000..9f35c74
--- /dev/null
+++ b/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupHandler.java
@@ -0,0 +1,353 @@
+/*
+ * 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 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.onlab.packet.MplsLabel;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.GroupId;
+import org.onosproject.segmentrouting.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.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<PolicyGroupIdentifier, PolicyGroupIdentifier> dependentGroups =
+ new HashMap<PolicyGroupIdentifier, PolicyGroupIdentifier>();
+
+ /**
+ * Policy group handler constructor.
+ *
+ * @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
+ */
+ public PolicyGroupHandler(DeviceId deviceId,
+ ApplicationId appId,
+ DeviceProperties config,
+ LinkService linkService,
+ GroupService groupService) {
+ super(deviceId, appId, config, linkService, groupService);
+ }
+
+ public PolicyGroupIdentifier createPolicyGroupChain(String id,
+ List<PolicyGroupParams> params) {
+ List<GroupBucketIdentifier> bucketIds = new ArrayList<GroupBucketIdentifier>();
+ for (PolicyGroupParams param: params) {
+ List<PortNumber> 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<GroupBucket> outBuckets = new ArrayList<GroupBucket>();
+ 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(MplsLabel.mplsLabel(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<GroupBucket> outBuckets = new ArrayList<GroupBucket>();
+ 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);
+ if (bucketId.label() != NeighborSet.NO_EDGE_LABEL) {
+ tBuilder.pushMpls()
+ .setMpls(MplsLabel.mplsLabel(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 = 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<GroupBucket> outBuckets = new ArrayList<GroupBucket>();
+ for (GroupBucketIdentifier bucketId:
+ dependentGroupKey.bucketIds()) {
+ TrafficTreatment.Builder tBuilder =
+ DefaultTrafficTreatment.builder();
+ if (bucketId.label() != NeighborSet.NO_EDGE_LABEL) {
+ tBuilder.pushMpls()
+ .setMpls(MplsLabel.
+ mplsLabel(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,
+ getGroupKey(bucketId.
+ outGroup())) == null) {
+ throw new IllegalStateException();
+ }
+ GroupId indirectGroupId = groupService.
+ getGroup(deviceId,
+ getGroupKey(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 PolicyGroupIdentifier generatePolicyGroupKey(String id,
+ List<PolicyGroupParams> params) {
+ List<GroupBucketIdentifier> bucketIds = new ArrayList<GroupBucketIdentifier>();
+ for (PolicyGroupParams param: params) {
+ List<PortNumber> 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(PolicyGroupIdentifier key) {
+ if (!(key instanceof PolicyGroupIdentifier)) {
+ throw new IllegalArgumentException();
+ }
+ List<PolicyGroupIdentifier> groupsToBeDeleted =
+ new ArrayList<PolicyGroupIdentifier>();
+ groupsToBeDeleted.add(key);
+
+ Iterator<PolicyGroupIdentifier> it =
+ groupsToBeDeleted.iterator();
+
+ while (it.hasNext()) {
+ PolicyGroupIdentifier innerMostGroupKey = it.next();
+ for (GroupBucketIdentifier bucketId:
+ innerMostGroupKey.bucketIds()) {
+ if (bucketId.type() != BucketOutputType.GROUP) {
+ groupsToBeDeleted.add(bucketId.outGroup());
+ }
+ }
+ groupService.removeGroup(deviceId,
+ getGroupKey(innerMostGroupKey),
+ appId);
+ it.remove();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupIdentifier.java b/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupIdentifier.java
new file mode 100644
index 0000000..44a0a2c
--- /dev/null
+++ b/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupIdentifier.java
@@ -0,0 +1,90 @@
+/*
+ * 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 java.util.List;
+
+/**
+ * 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 {
+ private String id;
+ private List<PolicyGroupParams> inputParams;
+ private List<GroupBucketIdentifier> 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<PolicyGroupParams> input,
+ List<GroupBucketIdentifier> 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<GroupBucketIdentifier> 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/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupParams.java b/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupParams.java
new file mode 100644
index 0000000..5a3f26b
--- /dev/null
+++ b/src/main/java/org/onosproject/segmentrouting/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.segmentrouting.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<PortNumber> ports;
+ private final List<Integer> 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<Integer> labelStack,
+ List<PortNumber> 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<PortNumber> getPorts() {
+ return ports;
+ }
+
+ /**
+ * Returns the label stack associated with the policy group params.
+ *
+ * @return list of integers
+ */
+ public List<Integer> 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;
+ }
+}