Partially extract segmentrouting-api

Change-Id: Iaf2a0bb387a6e7024fa3d75b42cb4874c93a09bb
diff --git a/api/pom.xml b/api/pom.xml
new file mode 100644
index 0000000..d222db2
--- /dev/null
+++ b/api/pom.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2015-present Open Networking Foundation
+  ~
+  ~ 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.
+  -->
+  <project xmlns="http://maven.apache.org/POM/4.0.0"
+              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+              xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onosproject</groupId>
+        <artifactId>segmentrouting</artifactId>
+        <version>3.0.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>segmentrouting-api</artifactId>
+    <packaging>bundle</packaging>
+    <url>http://trellisfabric.org</url>
+    <description>Trellis control api</description>
+
+    <dependencies>
+        <!-- ONOS core -->
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-api</artifactId>
+            <version>${onos.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Tests -->
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-api</artifactId>
+            <version>${onos.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/api/src/main/java/org/onosproject/segmentrouting/config/BlockedPortsConfig.java b/api/src/main/java/org/onosproject/segmentrouting/config/BlockedPortsConfig.java
new file mode 100644
index 0000000..a5c5557
--- /dev/null
+++ b/api/src/main/java/org/onosproject/segmentrouting/config/BlockedPortsConfig.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.config;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.config.Config;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Network config to describe ports that should be blocked until authenticated.
+ */
+public class BlockedPortsConfig extends Config<ApplicationId> {
+
+    /**
+     * Returns the top level keys to the config,
+     * which should be device ID strings.
+     *
+     * @return this list of top level keys
+     */
+    public List<String> deviceIds() {
+        List<String> devIds = new ArrayList<>();
+        if (object != null) {
+            Iterator<String> it = object.fieldNames();
+            if (it != null) {
+                it.forEachRemaining(devIds::add);
+            }
+        }
+        return devIds;
+    }
+
+    /**
+     * Returns the port range strings associated with the given device id key.
+     *
+     * @param deviceId the device id key
+     * @return the associated port range strings
+     */
+    public List<String> portRanges(String deviceId) {
+        List<String> portRanges = new ArrayList<>();
+        if (object != null) {
+            JsonNode jnode = object.get(deviceId);
+            if (ArrayNode.class.isInstance(jnode)) {
+                ArrayNode array = (ArrayNode) jnode;
+                array.forEach(pr -> portRanges.add(pr.asText()));
+            }
+        }
+        return portRanges;
+    }
+
+    /**
+     * Returns an iterator over the port numbers defined by the port ranges
+     * defined in the configuration, for the given device.
+     *
+     * @param deviceId the specific device
+     * @return an iterator over the configured ports
+     */
+    public Iterator<Long> portIterator(String deviceId) {
+        List<String> ranges = portRanges(deviceId);
+        return new PortIterator(ranges);
+    }
+
+    /**
+     * Private implementation of an iterator that aggregates several range
+     * iterators into a single iterator.
+     */
+    class PortIterator implements Iterator<Long> {
+        private final List<Range> ranges;
+        private final int nRanges;
+        private int currentRange = 0;
+        private Iterator<Long> iterator;
+
+        PortIterator(List<String> rangeSpecs) {
+            nRanges = rangeSpecs.size();
+            ranges = new ArrayList<>(nRanges);
+            if (nRanges > 0) {
+                for (String rs : rangeSpecs) {
+                    ranges.add(new Range(rs));
+                }
+                iterator = ranges.get(0).iterator();
+            }
+        }
+
+        @Override
+        public boolean hasNext() {
+            return nRanges > 0 &&
+                    (currentRange < nRanges - 1 ||
+                            (currentRange < nRanges && iterator.hasNext()));
+        }
+
+        @Override
+        public Long next() {
+            if (nRanges == 0) {
+                throw new NoSuchElementException();
+            }
+
+            Long value;
+            if (iterator.hasNext()) {
+                value = iterator.next();
+            } else {
+                currentRange++;
+                if (currentRange < nRanges) {
+                    iterator = ranges.get(currentRange).iterator();
+                    value = iterator.next();
+                } else {
+                    throw new NoSuchElementException();
+                }
+            }
+            return value;
+        }
+    }
+
+    /**
+     * Private implementation of a "range" of long numbers, defined by a
+     * string of the form {@code "<lo>-<hi>"}, for example, "17-32".
+     */
+    static final class Range {
+        private static final Pattern RE_SINGLE = Pattern.compile("(\\d+)");
+        private static final Pattern RE_RANGE = Pattern.compile("(\\d+)-(\\d+)");
+        private static final String E_BAD_FORMAT = "Bad Range Format ";
+
+        private final long lo;
+        private final long hi;
+
+        /**
+         * Constructs a range from the given string definition.
+         * For example:
+         * <pre>
+         *     Range r = new Range("17-32");
+         * </pre>
+         *
+         * @param s the string representation of the range
+         * @throws IllegalArgumentException if the range string is malformed
+         */
+        Range(String s) {
+            String lohi = s;
+            Matcher m = RE_SINGLE.matcher(s);
+            if (m.matches()) {
+                lohi = s + "-" + s;
+            }
+            m = RE_RANGE.matcher(lohi);
+            if (!m.matches()) {
+                throw new IllegalArgumentException(E_BAD_FORMAT + s);
+            }
+            try {
+                lo = Long.parseLong(m.group(1));
+                hi = Long.parseLong(m.group(2));
+
+                if (hi < lo) {
+                    throw new IllegalArgumentException(E_BAD_FORMAT + s);
+                }
+            } catch (NumberFormatException nfe) {
+                // unlikely to be thrown, since the matcher will have failed first
+                throw new IllegalArgumentException(E_BAD_FORMAT + s, nfe);
+            }
+        }
+
+
+        /**
+         * Returns an iterator over this range, starting from the lowest value
+         * and iterating up to the highest value (inclusive).
+         *
+         * @return an iterator over this range
+         */
+        Iterator<Long> iterator() {
+            return new RangeIterator();
+        }
+
+        /**
+         * Private implementation of an iterator over the range.
+         */
+        class RangeIterator implements Iterator<Long> {
+            long current;
+
+            RangeIterator() {
+                current = lo - 1;
+            }
+
+            @Override
+            public boolean hasNext() {
+                return current < hi;
+            }
+
+            @Override
+            public Long next() {
+                if (!hasNext()) {
+                    throw new NoSuchElementException();
+                }
+                return ++current;
+            }
+        }
+    }
+}
diff --git a/api/src/main/java/org/onosproject/segmentrouting/config/DeviceConfigNotFoundException.java b/api/src/main/java/org/onosproject/segmentrouting/config/DeviceConfigNotFoundException.java
new file mode 100644
index 0000000..773c7f6
--- /dev/null
+++ b/api/src/main/java/org/onosproject/segmentrouting/config/DeviceConfigNotFoundException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * 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.config;
+
+/**
+ * Signals that an error occurred during reading device configuration.
+ */
+public class DeviceConfigNotFoundException extends Exception {
+
+    /**
+     * Creates a new ConfigNotFoundException with the given message.
+     *
+     * @param message exception message
+     */
+    public DeviceConfigNotFoundException(String message) {
+        super(message);
+    }
+}
diff --git a/api/src/main/java/org/onosproject/segmentrouting/config/DeviceProperties.java b/api/src/main/java/org/onosproject/segmentrouting/config/DeviceProperties.java
new file mode 100644
index 0000000..2d0bbd2
--- /dev/null
+++ b/api/src/main/java/org/onosproject/segmentrouting/config/DeviceProperties.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * 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.config;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 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 {
+    /**
+     * Checks if the device is configured.
+     *
+     * @param deviceId device identifier
+     * @return true if the device is configured
+     */
+    boolean isConfigured(DeviceId deviceId);
+
+    /**
+     * Returns the IPv4 segment id of a device to be used in group creation.
+     *
+     * @param deviceId device identifier
+     * @throws DeviceConfigNotFoundException if the device configuration is not found
+     * @return segment id of a device
+     */
+    int getIPv4SegmentId(DeviceId deviceId) throws DeviceConfigNotFoundException;
+
+    /**
+     * Returns the IPv6 segment id of a device to be used in group creation.
+     *
+     * @param deviceId device identifier
+     * @throws DeviceConfigNotFoundException if the device configuration is not found
+     * @return segment id of a device
+     */
+    int getIPv6SegmentId(DeviceId deviceId) throws DeviceConfigNotFoundException;
+
+    /**
+     * Returns the Mac address of a device to be used in group creation.
+     *
+     * @param deviceId device identifier
+     * @throws DeviceConfigNotFoundException if the device configuration is not found
+     * @return mac address of a device
+     */
+    MacAddress getDeviceMac(DeviceId deviceId) throws DeviceConfigNotFoundException;
+
+    /**
+     *
+     * @param deviceId device identifier
+     * @throws DeviceConfigNotFoundException if the device configuration is not found
+     * @return the pseudowire routing label for a leaf node
+     */
+    int getPWRoutingLabel(DeviceId deviceId) throws DeviceConfigNotFoundException;
+
+    /**
+     * Returns the router ipv4 address of a segment router.
+     *
+     * @param deviceId device identifier
+     * @throws DeviceConfigNotFoundException if the device configuration is not found
+     * @return router ip address
+     */
+    IpAddress getRouterIpv4(DeviceId deviceId) throws DeviceConfigNotFoundException;
+
+    /**
+     * Returns the router ipv6 address of a segment router.
+     *
+     * @param deviceId device identifier
+     * @throws DeviceConfigNotFoundException if the device configuration is not found
+     * @return router ip address
+     */
+    IpAddress getRouterIpv6(DeviceId deviceId) throws DeviceConfigNotFoundException;
+
+    /**
+     * Indicates whether a device is edge device or transit/core device.
+     *
+     * @param deviceId device identifier
+     * @throws DeviceConfigNotFoundException if the device configuration is not found
+     * @return boolean
+     */
+    boolean isEdgeDevice(DeviceId deviceId) throws DeviceConfigNotFoundException;
+
+    /**
+     * Returns all segment IDs to be considered in building auto
+     *
+     * created groups.
+     * @return list of segment IDs
+     */
+    List<Integer> getAllDeviceSegmentIds();
+
+    /**
+     * Returns subnet-to-ports mapping of given device.
+     *
+     * For each entry of the map
+     * Key: a subnet
+     * Value: a list of ports, which are bound to the subnet
+     *
+     * @param deviceId device identifier
+     * @throws DeviceConfigNotFoundException if the device configuration is not found
+     * @return a map that contains all subnet-to-ports mapping of given device
+     */
+    Map<IpPrefix, List<PortNumber>> getSubnetPortsMap(DeviceId deviceId)
+            throws DeviceConfigNotFoundException;
+}
diff --git a/api/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingAppConfig.java b/api/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingAppConfig.java
new file mode 100644
index 0000000..de6ffb9
--- /dev/null
+++ b/api/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingAppConfig.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.config;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.google.common.collect.ImmutableSet;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.config.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Set;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * App configuration object for Segment Routing.
+ */
+public class SegmentRoutingAppConfig extends Config<ApplicationId> {
+
+    private static Logger log = LoggerFactory.getLogger(SegmentRoutingAppConfig.class);
+
+    private static final String VROUTER_MACS = "vRouterMacs";
+    private static final String SUPPRESS_SUBNET = "suppressSubnet";
+    private static final String SUPPRESS_HOST_BY_PORT = "suppressHostByPort";
+    // TODO We might want to move SUPPRESS_HOST_BY_PROVIDER to Component Config
+    private static final String SUPPRESS_HOST_BY_PROVIDER = "suppressHostByProvider";
+    private static final String MPLS_ECMP = "MPLS-ECMP";
+    private static final String BLACKHOLE_IPS = "blackholeIps";
+
+    @Override
+    public boolean isValid() {
+        return hasOnlyFields(VROUTER_MACS, SUPPRESS_SUBNET,
+                SUPPRESS_HOST_BY_PORT, SUPPRESS_HOST_BY_PROVIDER, MPLS_ECMP, BLACKHOLE_IPS) &&
+                vRouterMacs() != null &&
+                suppressSubnet() != null && suppressHostByPort() != null &&
+                suppressHostByProvider() != null &&
+                blackholeIPs() != null;
+    }
+
+    /**
+     * Gets ips to blackhole from the config.
+     *
+     * @return Set of ips to blackhole, empty is not specified,
+     * or null if not valid
+     */
+    public Set<IpPrefix> blackholeIPs() {
+        if (!object.has(BLACKHOLE_IPS)) {
+            return ImmutableSet.of();
+        }
+
+        ImmutableSet.Builder<IpPrefix> builder = ImmutableSet.builder();
+        ArrayNode arrayNode = (ArrayNode) object.path(BLACKHOLE_IPS);
+        for (JsonNode jsonNode : arrayNode) {
+            IpPrefix address;
+
+            String addrStr = jsonNode.asText(null);
+            if (addrStr != null) {
+                try {
+                    address = IpPrefix.valueOf(addrStr);
+                    builder.add(address);
+                } catch (IllegalArgumentException e) {
+                    log.debug("Not adding {}", jsonNode, e);
+                }
+            }
+        }
+        return builder.build();
+    }
+
+    /**
+     * Sets ips to blackhole to the config.
+     *
+     * @param blackholeIps a set of ips to blackhole
+     * @return this {@link SegmentRoutingAppConfig}
+     */
+    public SegmentRoutingAppConfig setBalckholeIps(Set<IpPrefix> blackholeIps) {
+        if (blackholeIps == null) {
+            object.remove(BLACKHOLE_IPS);
+        } else {
+            ArrayNode arrayNode = mapper.createArrayNode();
+
+            blackholeIps.forEach(ip -> {
+                arrayNode.add(ip.toString());
+            });
+
+            object.set(BLACKHOLE_IPS, arrayNode);
+        }
+        return this;
+    }
+
+    /**
+     * Gets MPLS-ECMP configuration from the config.
+     *
+     * @return the configuration of MPLS-ECMP. If it is not
+     *         specified, the default behavior is false.
+     */
+    public boolean mplsEcmp() {
+        return get(MPLS_ECMP, false);
+    }
+
+    /**
+     * Sets MPLS-ECMP to the config.
+     *
+     * @param mplsEcmp the MPLS-ECMP configuration
+     * @return this {@link SegmentRoutingAppConfig}
+     */
+    public SegmentRoutingAppConfig setMplsEcmp(boolean mplsEcmp) {
+        object.put(MPLS_ECMP, mplsEcmp);
+        return this;
+    }
+
+    /**
+     * Gets vRouters from the config.
+     *
+     * @return Set of vRouter MAC addresses, empty is not specified,
+     *         or null if not valid
+     */
+    public Set<MacAddress> vRouterMacs() {
+        if (!object.has(VROUTER_MACS)) {
+            return ImmutableSet.of();
+        }
+
+        ImmutableSet.Builder<MacAddress> builder = ImmutableSet.builder();
+        ArrayNode arrayNode = (ArrayNode) object.path(VROUTER_MACS);
+        for (JsonNode jsonNode : arrayNode) {
+            MacAddress mac;
+
+            String macStr = jsonNode.asText(null);
+            if (macStr == null) {
+                return null;
+            }
+            try {
+                mac = MacAddress.valueOf(macStr);
+            } catch (IllegalArgumentException e) {
+                return null;
+            }
+
+            builder.add(mac);
+        }
+        return builder.build();
+    }
+
+    /**
+     * Sets vRouters to the config.
+     *
+     * @param vRouterMacs a set of vRouter MAC addresses
+     * @return this {@link SegmentRoutingAppConfig}
+     */
+    public SegmentRoutingAppConfig setVRouterMacs(Set<MacAddress> vRouterMacs) {
+        if (vRouterMacs == null) {
+            object.remove(VROUTER_MACS);
+        } else {
+            ArrayNode arrayNode = mapper.createArrayNode();
+
+            vRouterMacs.forEach(mac -> {
+                arrayNode.add(mac.toString());
+            });
+
+            object.set(VROUTER_MACS, arrayNode);
+        }
+        return this;
+    }
+
+    /**
+     * Gets names of ports to which SegmentRouting does not push subnet rules.
+     *
+     * @return Set of port names, empty if not specified, or null
+     *         if not valid
+     */
+    public Set<ConnectPoint> suppressSubnet() {
+        if (!object.has(SUPPRESS_SUBNET)) {
+            return ImmutableSet.of();
+        }
+
+        ImmutableSet.Builder<ConnectPoint> builder = ImmutableSet.builder();
+        ArrayNode arrayNode = (ArrayNode) object.path(SUPPRESS_SUBNET);
+        for (JsonNode jsonNode : arrayNode) {
+            String portName = jsonNode.asText(null);
+            if (portName == null) {
+                return null;
+            }
+            try {
+                builder.add(ConnectPoint.deviceConnectPoint(portName));
+            } catch (IllegalArgumentException e) {
+                return null;
+            }
+        }
+        return builder.build();
+    }
+
+    /**
+     * Sets names of ports to which SegmentRouting does not push subnet rules.
+     *
+     * @param suppressSubnet names of ports to which SegmentRouting does not push
+     *                     subnet rules
+     * @return this {@link SegmentRoutingAppConfig}
+     */
+    public SegmentRoutingAppConfig setSuppressSubnet(Set<ConnectPoint> suppressSubnet) {
+        if (suppressSubnet == null) {
+            object.remove(SUPPRESS_SUBNET);
+        } else {
+            ArrayNode arrayNode = mapper.createArrayNode();
+            suppressSubnet.forEach(connectPoint -> {
+                arrayNode.add(connectPoint.deviceId() + "/" + connectPoint.port());
+            });
+            object.set(SUPPRESS_SUBNET, arrayNode);
+        }
+        return this;
+    }
+
+    /**
+     * Gets connect points to which SegmentRouting does not push host rules.
+     *
+     * @return Set of connect points, empty if not specified, or null
+     *         if not valid
+     */
+    public Set<ConnectPoint> suppressHostByPort() {
+        if (!object.has(SUPPRESS_HOST_BY_PORT)) {
+            return ImmutableSet.of();
+        }
+
+        ImmutableSet.Builder<ConnectPoint> builder = ImmutableSet.builder();
+        ArrayNode arrayNode = (ArrayNode) object.path(SUPPRESS_HOST_BY_PORT);
+        for (JsonNode jsonNode : arrayNode) {
+            String portName = jsonNode.asText(null);
+            if (portName == null) {
+                return null;
+            }
+            try {
+                builder.add(ConnectPoint.deviceConnectPoint(portName));
+            } catch (IllegalArgumentException e) {
+                return null;
+            }
+        }
+        return builder.build();
+    }
+
+    /**
+     * Sets connect points to which SegmentRouting does not push host rules.
+     *
+     * @param connectPoints connect points to which SegmentRouting does not push
+     *                     host rules
+     * @return this {@link SegmentRoutingAppConfig}
+     */
+    public SegmentRoutingAppConfig setSuppressHostByPort(Set<ConnectPoint> connectPoints) {
+        if (connectPoints == null) {
+            object.remove(SUPPRESS_HOST_BY_PORT);
+        } else {
+            ArrayNode arrayNode = mapper.createArrayNode();
+            connectPoints.forEach(connectPoint -> {
+                arrayNode.add(connectPoint.deviceId() + "/" + connectPoint.port());
+            });
+            object.set(SUPPRESS_HOST_BY_PORT, arrayNode);
+        }
+        return this;
+    }
+
+    /**
+     * Gets provider names from which SegmentRouting does not learn host info.
+     *
+     * @return array of provider names that need to be ignored
+     */
+    public Set<String> suppressHostByProvider() {
+        if (!object.has(SUPPRESS_HOST_BY_PROVIDER)) {
+            return ImmutableSet.of();
+        }
+
+        ImmutableSet.Builder<String> builder = ImmutableSet.builder();
+        ArrayNode arrayNode = (ArrayNode) object.path(SUPPRESS_HOST_BY_PROVIDER);
+        for (JsonNode jsonNode : arrayNode) {
+            String providerName = jsonNode.asText(null);
+            if (providerName == null) {
+                return null;
+            }
+            builder.add(providerName);
+        }
+        return builder.build();
+    }
+
+    /**
+     * Sets provider names from which SegmentRouting does not learn host info.
+     *
+     * @param providers set of provider names
+     * @return this {@link SegmentRoutingAppConfig}
+     */
+    public SegmentRoutingAppConfig setSuppressHostByProvider(Set<String> providers) {
+        if (providers == null) {
+            object.remove(SUPPRESS_HOST_BY_PROVIDER);
+        } else {
+            ArrayNode arrayNode = mapper.createArrayNode();
+            providers.forEach(arrayNode::add);
+            object.set(SUPPRESS_HOST_BY_PROVIDER, arrayNode);
+        }
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("vRouterMacs", vRouterMacs())
+                .add("suppressSubnet", suppressSubnet())
+                .add("suppressHostByPort", suppressHostByPort())
+                .add("suppressHostByProvider", suppressHostByProvider())
+                .add("mplsEcmp", mplsEcmp())
+                .add("blackholeIps", blackholeIPs())
+                .toString();
+    }
+}
diff --git a/api/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfig.java b/api/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfig.java
new file mode 100644
index 0000000..7959df6
--- /dev/null
+++ b/api/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfig.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.config;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableMap;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.Config;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Configuration object for Segment Routing Application.
+ */
+public class SegmentRoutingDeviceConfig extends Config<DeviceId> {
+    private static final String NAME = "name";
+    private static final String IP4 = "ipv4Loopback";
+    private static final String IP6 = "ipv6Loopback";
+    private static final String MAC = "routerMac";
+    private static final String IP4_SID = "ipv4NodeSid";
+    private static final String IP6_SID = "ipv6NodeSid";
+    private static final String EDGE = "isEdgeRouter";
+    /**
+     * Adjancency SIDs config.
+     *
+     * @deprecated in Loon (1.11). We are not using and do not plan to use it.
+     */
+    @Deprecated
+    private static final String ADJSIDS = "adjacencySids";
+
+    /**
+     * Adjancency SID config.
+     *
+     * @deprecated in Loon (1.11). We are not using and do not plan to use it.
+     */
+    @Deprecated
+    private static final String ADJSID = "adjSid";
+
+    /**
+     * Adjancency port config.
+     *
+     * @deprecated in Loon (1.11). We are not using and do not plan to use it.
+     */
+    @Deprecated
+    private static final String PORTS = "ports";
+
+    private static final String PAIR_DEVICE_ID = "pairDeviceId";
+    private static final String PAIR_LOCAL_PORT = "pairLocalPort";
+
+    @Override
+    public boolean isValid() {
+        return hasOnlyFields(NAME, IP4, IP6, MAC, IP4_SID, IP6_SID, EDGE, ADJSIDS, ADJSID, PORTS,
+                             PAIR_DEVICE_ID, PAIR_LOCAL_PORT) &&
+                name() != null &&
+                routerIpv4() != null && (!hasField(IP6) || routerIpv6() != null) &&
+                routerMac() != null &&
+                nodeSidIPv4() != -1 && (!hasField(IP6_SID) || nodeSidIPv6() != -1) &&
+                isEdgeRouter() != null &&
+                adjacencySids() != null &&
+                // pairDeviceId and pairLocalPort must be both configured or both omitted
+                (hasField(PAIR_DEVICE_ID) == hasField(PAIR_LOCAL_PORT)) &&
+                (!hasField(PAIR_DEVICE_ID) || pairDeviceId() != null) &&
+                (!hasField(PAIR_LOCAL_PORT) || pairLocalPort() != null);
+    }
+
+    /**
+     * Gets the name of the router.
+     *
+     * @return Optional name of the router. May be empty if not configured.
+     */
+    public Optional<String> name() {
+        String name = get(NAME, null);
+        return name != null ? Optional.of(name) : Optional.empty();
+    }
+
+    /**
+     * Sets the name of the router.
+     *
+     * @param name name of the router.
+     * @return the config of the router.
+     */
+    public SegmentRoutingDeviceConfig setName(String name) {
+        return (SegmentRoutingDeviceConfig) setOrClear(NAME, name);
+    }
+
+    /**
+     * Gets the IPv4 address of the router.
+     *
+     * @return IP address of the router. Or null if not configured.
+     */
+    public Ip4Address routerIpv4() {
+        String ip = get(IP4, null);
+        return ip != null ? Ip4Address.valueOf(ip) : null;
+    }
+
+    /**
+     * Gets the IPv6 address of the router.
+     *
+     * @return IP address of the router. Or null if not configured.
+     */
+    public Ip6Address routerIpv6() {
+        String ip = get(IP6, null);
+        return ip != null ? Ip6Address.valueOf(ip) : null;
+    }
+
+    /**
+     * Sets the IPv4 address of the router.
+     *
+     * @param ip IPv4 address of the router.
+     * @return the config of the router.
+     */
+    public SegmentRoutingDeviceConfig setRouterIpv4(String ip) {
+        return (SegmentRoutingDeviceConfig) setOrClear(IP4, ip);
+    }
+
+    /**
+     * Sets the IPv6 address of the router.
+     *
+     * @param ip IPv6 address of the router.
+     * @return the config of the router.
+     */
+    public SegmentRoutingDeviceConfig setRouterIpv6(String ip) {
+        return (SegmentRoutingDeviceConfig) setOrClear(IP6, ip);
+    }
+
+    /**
+     * Gets the MAC address of the router.
+     *
+     * @return MAC address of the router. Or null if not configured.
+     */
+    public MacAddress routerMac() {
+        String mac = get(MAC, null);
+        return mac != null ? MacAddress.valueOf(mac) : null;
+    }
+
+    /**
+     * Sets the MAC address of the router.
+     *
+     * @param mac MAC address of the router.
+     * @return the config of the router.
+     */
+    public SegmentRoutingDeviceConfig setRouterMac(String mac) {
+        return (SegmentRoutingDeviceConfig) setOrClear(MAC, mac);
+    }
+
+    /**
+     * Gets the IPv4 node SID of the router.
+     *
+     * @return node SID of the router. Or -1 if not configured.
+     */
+    public int nodeSidIPv4() {
+        return get(IP4_SID, -1);
+    }
+
+    /**
+     * Gets the IPv6 node SID of the router.
+     *
+     * @return node SID of the router. Or -1 if not configured.
+     */
+    public int nodeSidIPv6() {
+        return get(IP6_SID, -1);
+    }
+
+    /**
+     * Sets the node IPv4 node SID of the router.
+     *
+     * @param sid node SID of the router.
+     * @return the config of the router.
+     */
+    public SegmentRoutingDeviceConfig setNodeSidIPv4(int sid) {
+        return (SegmentRoutingDeviceConfig) setOrClear(IP4_SID, sid);
+    }
+
+    /**
+     * Sets the node IPv6 node SID of the router.
+     *
+     * @param sid node SID of the router.
+     * @return the config of the router.
+     */
+    public SegmentRoutingDeviceConfig setNodeSidIPv6(int sid) {
+        return (SegmentRoutingDeviceConfig) setOrClear(IP6_SID, sid);
+    }
+
+    /**
+     * Checks if the router is an edge router.
+     *
+     * @return true if the router is an edge router.
+     *         false if the router is not an edge router.
+     *         null if the value is not configured.
+     */
+    public Boolean isEdgeRouter() {
+        String isEdgeRouter = get(EDGE, null);
+        return isEdgeRouter != null ?
+                Boolean.valueOf(isEdgeRouter) :
+                null;
+    }
+
+    /**
+     * Specifies if the router is an edge router.
+     *
+     * @param isEdgeRouter true if the router is an edge router.
+     * @return the config of the router.
+     */
+    public SegmentRoutingDeviceConfig setIsEdgeRouter(boolean isEdgeRouter) {
+        return (SegmentRoutingDeviceConfig) setOrClear(EDGE, isEdgeRouter);
+    }
+
+    /**
+     * Gets the adjacency SIDs of the router.
+     *
+     * @return adjacency SIDs of the router. Or null if not configured.
+     */
+    public Map<Integer, Set<Integer>> adjacencySids() {
+        if (!object.has(ADJSIDS)) {
+            return null;
+        }
+
+        Map<Integer, Set<Integer>> adjacencySids = new HashMap<>();
+        ArrayNode adjacencySidsNode = (ArrayNode) object.path(ADJSIDS);
+        for (JsonNode adjacencySidNode : adjacencySidsNode) {
+            int asid = adjacencySidNode.path(ADJSID).asInt(-1);
+            if (asid == -1) {
+                return null;
+            }
+
+            HashSet<Integer> ports = new HashSet<>();
+            ArrayNode portsNode = (ArrayNode) adjacencySidNode.path(PORTS);
+            for (JsonNode portNode : portsNode) {
+                int port = portNode.asInt(-1);
+                if (port == -1) {
+                    return null;
+                }
+                ports.add(port);
+            }
+            adjacencySids.put(asid, ports);
+        }
+
+        return ImmutableMap.copyOf(adjacencySids);
+    }
+
+    /**
+     * Sets the adjacency SIDs of the router.
+     *
+     * @param adjacencySids adjacency SIDs of the router.
+     * @return the config of the router.
+     */
+    public SegmentRoutingDeviceConfig setAdjacencySids(Map<Integer, Set<Integer>> adjacencySids) {
+        if (adjacencySids == null) {
+            object.remove(ADJSIDS);
+        } else {
+            ArrayNode adjacencySidsNode = mapper.createArrayNode();
+
+            adjacencySids.forEach((sid, ports) -> {
+                ObjectNode adjacencySidNode = mapper.createObjectNode();
+
+                adjacencySidNode.put(ADJSID, sid);
+
+                ArrayNode portsNode = mapper.createArrayNode();
+                ports.forEach(port -> {
+                    portsNode.add(port.toString());
+                });
+                adjacencySidNode.set(PORTS, portsNode);
+
+                adjacencySidsNode.add(adjacencySidNode);
+            });
+
+            object.set(ADJSIDS, adjacencySidsNode);
+        }
+
+        return this;
+    }
+
+    /**
+     * Gets the pair device id.
+     *
+     * @return pair device id; Or null if not configured.
+     */
+    public DeviceId pairDeviceId() {
+        String pairDeviceId = get(PAIR_DEVICE_ID, null);
+        return pairDeviceId != null ? DeviceId.deviceId(pairDeviceId) : null;
+    }
+
+    /**
+     * Sets the pair device id.
+     *
+     * @param deviceId pair device id
+     * @return this configuration
+     */
+    public SegmentRoutingDeviceConfig setPairDeviceId(DeviceId deviceId) {
+        return (SegmentRoutingDeviceConfig) setOrClear(PAIR_DEVICE_ID, deviceId.toString());
+    }
+
+    /**
+     * Gets the pair local port.
+     *
+     * @return pair local port; Or null if not configured.
+     */
+    public PortNumber pairLocalPort() {
+        long pairLocalPort = get(PAIR_LOCAL_PORT, -1L);
+        return pairLocalPort != -1L ? PortNumber.portNumber(pairLocalPort) : null;
+    }
+
+    /**
+     * Sets the pair local port.
+     *
+     * @param portNumber pair local port
+     * @return this configuration
+     */
+    public SegmentRoutingDeviceConfig setPairLocalPort(PortNumber portNumber) {
+        return (SegmentRoutingDeviceConfig) setOrClear(PAIR_LOCAL_PORT, portNumber.toLong());
+    }
+}
diff --git a/api/src/main/java/org/onosproject/segmentrouting/config/package-info.java b/api/src/main/java/org/onosproject/segmentrouting/config/package-info.java
new file mode 100644
index 0000000..a664a8f
--- /dev/null
+++ b/api/src/main/java/org/onosproject/segmentrouting/config/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Segment routing network configuration mechanism.
+ */
+package org.onosproject.segmentrouting.config;
\ No newline at end of file
diff --git a/api/src/test/java/org/onosproject/segmentrouting/config/BlockedPortsConfigTest.java b/api/src/test/java/org/onosproject/segmentrouting/config/BlockedPortsConfigTest.java
new file mode 100644
index 0000000..e457896
--- /dev/null
+++ b/api/src/test/java/org/onosproject/segmentrouting/config/BlockedPortsConfigTest.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.config;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.DefaultApplicationId;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Unit tests for {@link BlockedPortsConfig}.
+ */
+public class BlockedPortsConfigTest {
+
+    private static final ApplicationId APP_ID = new DefaultApplicationId(1, "foo");
+    private static final String KEY = "blocked";
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    private static final String DEV1 = "of:0000000000000001";
+    private static final String DEV2 = "of:0000000000000002";
+    private static final String DEV3 = "of:0000000000000003";
+    private static final String DEV4 = "of:0000000000000004";
+    private static final String RANGE_14 = "1-4";
+    private static final String RANGE_79 = "7-9";
+    private static final String P1 = "1";
+    private static final String P5 = "5";
+    private static final String P9 = "9";
+
+    private BlockedPortsConfig cfg;
+    private BlockedPortsConfig.Range range;
+
+    private void print(String s) {
+        System.out.println(s);
+    }
+
+    private void print(Object o) {
+        print(o.toString());
+    }
+
+    @Before
+    public void setUp() throws IOException {
+        InputStream blockedPortsJson = BlockedPortsConfigTest.class
+                .getResourceAsStream("/blocked-ports.json");
+        JsonNode node = MAPPER.readTree(blockedPortsJson);
+        cfg = new BlockedPortsConfig();
+        cfg.init(APP_ID, KEY, node, MAPPER, null);
+    }
+
+    @Test
+    public void basic() {
+        cfg = new BlockedPortsConfig();
+        print(cfg);
+
+        assertEquals("non-empty devices list", 0, cfg.deviceIds().size());
+        assertEquals("non-empty port-ranges list", 0, cfg.portRanges("non-exist").size());
+    }
+
+
+    @Test
+    public void overIteratePort() {
+        Iterator<Long> iterator = cfg.portIterator(DEV3);
+        while (iterator.hasNext()) {
+            print(iterator.next());
+        }
+
+        try {
+            print(iterator.next());
+            fail("NoSuchElement exception NOT thrown");
+        } catch (NoSuchElementException e) {
+            print("<good> " + e);
+        }
+    }
+
+    @Test
+    public void overIterateRange() {
+        range = new BlockedPortsConfig.Range("4-6");
+
+        Iterator<Long> iterator = range.iterator();
+        while (iterator.hasNext()) {
+            print(iterator.next());
+        }
+
+        try {
+            print(iterator.next());
+            fail("NoSuchElement exception NOT thrown");
+        } catch (NoSuchElementException e) {
+            print("<good> " + e);
+        }
+    }
+
+
+    @Test
+    public void simple() {
+        List<String> devIds = cfg.deviceIds();
+        print(devIds);
+        assertEquals("wrong dev id count", 3, devIds.size());
+        assertEquals("missing dev 1", true, devIds.contains(DEV1));
+        assertEquals("dev 2??", false, devIds.contains(DEV2));
+        assertEquals("missing dev 3", true, devIds.contains(DEV3));
+
+        List<String> d1ranges = cfg.portRanges(DEV1);
+        print(d1ranges);
+        assertEquals("wrong d1 range count", 2, d1ranges.size());
+        assertEquals("missing 1-4", true, d1ranges.contains(RANGE_14));
+        assertEquals("missing 7-9", true, d1ranges.contains(RANGE_79));
+
+        List<String> d2ranges = cfg.portRanges(DEV2);
+        print(d2ranges);
+        assertEquals("wrong d2 range count", 0, d2ranges.size());
+
+        List<String> d3ranges = cfg.portRanges(DEV3);
+        print(d3ranges);
+        assertEquals("wrong d3 range count", 1, d3ranges.size());
+        assertEquals("range 1-4?", false, d3ranges.contains(RANGE_14));
+        assertEquals("missing 7-9", true, d3ranges.contains(RANGE_79));
+    }
+
+
+    private void verifyPorts(List<Long> ports, long... exp) {
+        assertEquals("Wrong port count", exp.length, ports.size());
+        for (long e : exp) {
+            assertEquals("missing port", true, ports.contains(e));
+        }
+    }
+
+    private void verifyPortIterator(String devid, long... exp) {
+        List<Long> ports = new ArrayList<>();
+        Iterator<Long> iter = cfg.portIterator(devid);
+        iter.forEachRemaining(ports::add);
+        print(ports);
+        verifyPorts(ports, exp);
+    }
+
+    @Test
+    public void rangeIterators() {
+        verifyPortIterator(DEV1, 1, 2, 3, 4, 7, 8, 9);
+        verifyPortIterator(DEV2);
+        verifyPortIterator(DEV3, 7, 8, 9);
+    }
+
+    @Test
+    public void singlePorts() {
+        List<String> devIds = cfg.deviceIds();
+        print(devIds);
+        assertEquals("wrong dev id count", 3, devIds.size());
+        assertEquals("missing dev 4", true, devIds.contains(DEV4));
+
+        List<String> d1ranges = cfg.portRanges(DEV4);
+        print(d1ranges);
+        assertEquals("wrong d4 range count", 3, d1ranges.size());
+        assertEquals("missing 1", true, d1ranges.contains(P1));
+        assertEquals("missing 5", true, d1ranges.contains(P5));
+        assertEquals("missing 9", true, d1ranges.contains(P9));
+
+        verifyPortIterator(DEV4, 1, 5, 9);
+    }
+
+
+    // test Range inner class
+
+    @Test
+    public void rangeBadFormat() {
+        try {
+            range = new BlockedPortsConfig.Range("not-a-range-format");
+            fail("no exception thrown");
+        } catch (IllegalArgumentException iar) {
+            print(iar);
+            assertEquals("wrong msg", "Bad Range Format not-a-range-format", iar.getMessage());
+        }
+    }
+
+    @Test
+    public void rangeBadHi() {
+        try {
+            range = new BlockedPortsConfig.Range("2-nine");
+            fail("no exception thrown");
+        } catch (IllegalArgumentException iar) {
+            print(iar);
+            assertEquals("wrong msg", "Bad Range Format 2-nine", iar.getMessage());
+        }
+    }
+
+    @Test
+    public void rangeHiLessThanLo() {
+        try {
+            range = new BlockedPortsConfig.Range("9-5");
+            fail("no exception thrown");
+        } catch (IllegalArgumentException iar) {
+            print(iar);
+            assertEquals("wrong msg", "Bad Range Format 9-5", iar.getMessage());
+        }
+    }
+
+    @Test
+    public void rangeNegative() {
+        try {
+            range = new BlockedPortsConfig.Range("-2-4");
+            fail("no exception thrown");
+        } catch (IllegalArgumentException iar) {
+            print(iar);
+            assertEquals("wrong msg", "Bad Range Format -2-4", iar.getMessage());
+        }
+    }
+
+    @Test
+    public void rangeGood() {
+        range = new BlockedPortsConfig.Range("100-104");
+        List<Long> values = new ArrayList<>();
+        range.iterator().forEachRemaining(values::add);
+        print(values);
+        verifyPorts(values, 100, 101, 102, 103, 104);
+    }
+}
diff --git a/api/src/test/java/org/onosproject/segmentrouting/config/SegmentRoutingAppConfigTest.java b/api/src/test/java/org/onosproject/segmentrouting/config/SegmentRoutingAppConfigTest.java
new file mode 100644
index 0000000..fa667c0
--- /dev/null
+++ b/api/src/test/java/org/onosproject/segmentrouting/config/SegmentRoutingAppConfigTest.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.config;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableSet;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.TestApplicationId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.config.ConfigApplyDelegate;
+
+import java.io.InputStream;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.*;
+
+/**
+ * Tests for class {@link SegmentRoutingAppConfig}.
+ */
+public class SegmentRoutingAppConfigTest {
+    private SegmentRoutingAppConfig config;
+    private SegmentRoutingAppConfig invalidConfig;
+    private SegmentRoutingAppConfig mplsEcmpConfig;
+
+    private static final String APP_NAME = "org.onosproject.segmentrouting";
+    private static final MacAddress ROUTER_MAC_1 = MacAddress.valueOf("00:00:00:00:00:01");
+    private static final MacAddress ROUTER_MAC_2 = MacAddress.valueOf("00:00:00:00:00:02");
+    private static final MacAddress ROUTER_MAC_3 = MacAddress.valueOf("00:00:00:00:00:03");
+    private static final ConnectPoint PORT_1 = ConnectPoint.deviceConnectPoint("of:1/1");
+    private static final ConnectPoint PORT_2 = ConnectPoint.deviceConnectPoint("of:1/2");
+    private static final ConnectPoint PORT_3 = ConnectPoint.deviceConnectPoint("of:1/3");
+    private static final DeviceId VROUTER_ID_1 = DeviceId.deviceId("of:1");
+    private static final DeviceId VROUTER_ID_2 = DeviceId.deviceId("of:2");
+    private static final String PROVIDER_1 = "org.onosproject.provider.host";
+    private static final String PROVIDER_2 = "org.onosproject.netcfghost";
+    private static final String PROVIDER_3 = "org.onosproject.anotherprovider";
+    private static final IpPrefix BLACKHOLE_IP = IpPrefix.valueOf("10.0.0.0/8");
+    private static final IpPrefix BLACKHOLE_IP_2 = IpPrefix.valueOf("20.0.0.0/8");
+
+    /**
+     * Initialize test related variables.
+     *
+     * @throws Exception
+     */
+    @Before
+    public void setUp() throws Exception {
+        InputStream jsonStream = SegmentRoutingAppConfigTest.class
+                .getResourceAsStream("/app.json");
+        InputStream invalidJsonStream = SegmentRoutingAppConfigTest.class
+                .getResourceAsStream("/app-invalid.json");
+        InputStream mplsEcmpJsonStream = SegmentRoutingAppConfigTest.class
+                .getResourceAsStream("/app-ecmp.json");
+
+        String key = APP_NAME;
+        ApplicationId subject = new TestApplicationId(key);
+        ObjectMapper mapper = new ObjectMapper();
+        JsonNode jsonNode = mapper.readTree(jsonStream);
+        JsonNode invalidJsonNode = mapper.readTree(invalidJsonStream);
+        JsonNode mplsEcmpJsonNode = mapper.readTree(mplsEcmpJsonStream);
+        ConfigApplyDelegate delegate = new MockDelegate();
+
+        config = new SegmentRoutingAppConfig();
+        config.init(subject, key, jsonNode, mapper, delegate);
+        invalidConfig = new SegmentRoutingAppConfig();
+        invalidConfig.init(subject, key, invalidJsonNode, mapper, delegate);
+        mplsEcmpConfig = new SegmentRoutingAppConfig();
+        mplsEcmpConfig.init(subject, key, mplsEcmpJsonNode, mapper, delegate);
+    }
+
+    /**
+     * Tests config validity.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testIsValid() throws Exception {
+        assertTrue(config.isValid());
+        assertFalse(invalidConfig.isValid());
+        assertTrue(mplsEcmpConfig.isValid());
+    }
+
+    /**
+     * Test MPLS-ECMP default getter. By-default
+     * MPLS-ECMPS is false.
+     */
+    @Test
+    public void testDefaultMplsEcmp() {
+        boolean mplsEcmp = config.mplsEcmp();
+        assertThat(mplsEcmp, is(false));
+    }
+
+    /**
+     * Test MPLS-ECMP getter.
+     */
+    @Test
+    public void testMplsEcmp() {
+        boolean mplsEcmp = mplsEcmpConfig.mplsEcmp();
+        assertThat(mplsEcmp, is(true));
+    }
+
+    /**
+     * Test MPLS-ECMP setter.
+     */
+    @Test
+    public void testSetMplsEcmp() {
+        /*
+         * In the config the value is not set.
+         */
+        boolean mplsEcmp = config.mplsEcmp();
+        assertThat(mplsEcmp, is(false));
+        config.setMplsEcmp(true);
+        mplsEcmp = config.mplsEcmp();
+        assertThat(mplsEcmp, is(true));
+        /*
+         * In the mplsEcmpConfig the value is true,
+         */
+        mplsEcmp = mplsEcmpConfig.mplsEcmp();
+        assertThat(mplsEcmp, is(true));
+        config.setMplsEcmp(false);
+        mplsEcmp = config.mplsEcmp();
+        assertThat(mplsEcmp, is(false));
+    }
+
+    /**
+     * Tests vRouterMacs getter.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testVRouterMacs() throws Exception {
+        Set<MacAddress> vRouterMacs = config.vRouterMacs();
+        assertNotNull("vRouterMacs should not be null", vRouterMacs);
+        assertThat(vRouterMacs.size(), is(2));
+        assertTrue(vRouterMacs.contains(ROUTER_MAC_1));
+        assertTrue(vRouterMacs.contains(ROUTER_MAC_2));
+    }
+
+    /**
+     * Tests vRouterMacs setter.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testSetVRouterMacs() throws Exception {
+        ImmutableSet.Builder<MacAddress> builder = ImmutableSet.builder();
+        builder.add(ROUTER_MAC_3);
+        config.setVRouterMacs(builder.build());
+
+        Set<MacAddress> vRouterMacs = config.vRouterMacs();
+        assertThat(vRouterMacs.size(), is(1));
+        assertTrue(vRouterMacs.contains(ROUTER_MAC_3));
+    }
+
+    /**
+     * Tests suppressSubnet getter.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testSuppressSubnet() throws Exception {
+        Set<ConnectPoint> suppressSubnet = config.suppressSubnet();
+        assertNotNull("suppressSubnet should not be null", suppressSubnet);
+        assertThat(suppressSubnet.size(), is(2));
+        assertTrue(suppressSubnet.contains(PORT_1));
+        assertTrue(suppressSubnet.contains(PORT_2));
+    }
+
+    /**
+     * Tests suppressSubnet setter.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testSetSuppressSubnet() throws Exception {
+        ImmutableSet.Builder<ConnectPoint> builder = ImmutableSet.builder();
+        builder.add(PORT_3);
+        config.setSuppressSubnet(builder.build());
+
+        Set<ConnectPoint> suppressSubnet = config.suppressSubnet();
+        assertNotNull("suppressSubnet should not be null", suppressSubnet);
+        assertThat(suppressSubnet.size(), is(1));
+        assertTrue(suppressSubnet.contains(PORT_3));
+    }
+
+    /**
+     * Tests suppressHostByPort getter.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testSuppressHostByPort() throws Exception {
+        Set<ConnectPoint> suppressHostByPort = config.suppressHostByPort();
+        assertNotNull("suppressHostByPort should not be null", suppressHostByPort);
+        assertThat(suppressHostByPort.size(), is(2));
+        assertTrue(suppressHostByPort.contains(PORT_1));
+        assertTrue(suppressHostByPort.contains(PORT_2));
+    }
+
+    /**
+     * Tests suppressHostByPort setter.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testSetSuppressHostByPort() throws Exception {
+        ImmutableSet.Builder<ConnectPoint> builder = ImmutableSet.builder();
+        builder.add(PORT_3);
+        config.setSuppressHostByPort(builder.build());
+
+        Set<ConnectPoint> suppressHostByPort = config.suppressHostByPort();
+        assertNotNull("suppressHostByPort should not be null", suppressHostByPort);
+        assertThat(suppressHostByPort.size(), is(1));
+        assertTrue(suppressHostByPort.contains(PORT_3));
+    }
+
+    /**
+     * Tests suppressHostByProvider getter.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testSuppressHostByProvider() throws Exception {
+        Set<String> supprsuppressHostByProvider = config.suppressHostByProvider();
+        assertNotNull("suppressHostByProvider should not be null", supprsuppressHostByProvider);
+        assertThat(supprsuppressHostByProvider.size(), is(2));
+        assertTrue(supprsuppressHostByProvider.contains(PROVIDER_1));
+        assertTrue(supprsuppressHostByProvider.contains(PROVIDER_2));
+    }
+
+    /**
+     * Tests suppressHostByProvider setter.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testSetHostLearning() throws Exception {
+        ImmutableSet.Builder<String> builder = ImmutableSet.builder();
+        builder.add(PROVIDER_3);
+        config.setSuppressHostByProvider(builder.build());
+
+        Set<String> supprsuppressHostByProvider = config.suppressHostByProvider();
+        assertNotNull("suppressHostByProvider should not be null", supprsuppressHostByProvider);
+        assertThat(supprsuppressHostByProvider.size(), is(1));
+        assertTrue(supprsuppressHostByProvider.contains(PROVIDER_3));
+    }
+
+    /**
+     * Tests BlackHoleIps getter.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testBlackHoleIps() throws Exception {
+        Set<IpPrefix> blackHoleIps = config.blackholeIPs();
+        assertNotNull("BlackHoleIps should not be null", blackHoleIps);
+        assertThat(blackHoleIps.size(), is(1));
+        assertTrue(blackHoleIps.contains(BLACKHOLE_IP));
+    }
+
+    /**
+     * Tests BlackHoleIps setter.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testSetBlackHoleIps() throws Exception {
+
+        config.setBalckholeIps(ImmutableSet.of(BLACKHOLE_IP_2));
+
+        Set<IpPrefix> blackHoleIps = config.blackholeIPs();
+        assertThat(blackHoleIps.size(), is(1));
+        assertTrue(blackHoleIps.contains(BLACKHOLE_IP_2));
+    }
+
+    private class MockDelegate implements ConfigApplyDelegate {
+        @Override
+        public void onApply(Config config) {
+        }
+    }
+}
diff --git a/api/src/test/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfigTest.java b/api/src/test/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfigTest.java
new file mode 100644
index 0000000..ddf90bb
--- /dev/null
+++ b/api/src/test/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfigTest.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.config;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.config.ConfigApplyDelegate;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for class {@link SegmentRoutingDeviceConfig}.
+ */
+public class SegmentRoutingDeviceConfigTest {
+    private SegmentRoutingDeviceConfig config;
+    private SegmentRoutingDeviceConfig ipv6Config;
+    private SegmentRoutingDeviceConfig pairConfig;
+    private SegmentRoutingDeviceConfig invalidConfig;
+    private Map<Integer, Set<Integer>> adjacencySids1;
+    private Map<Integer, Set<Integer>> adjacencySids2;
+    private static final DeviceId PAIR_DEVICE_ID = DeviceId.deviceId("of:123456789ABCDEF0");
+    private static final PortNumber PAIR_LOCAL_PORT = PortNumber.portNumber(10);
+
+    @Before
+    public void setUp() throws Exception {
+        InputStream jsonStream = SegmentRoutingDeviceConfigTest.class
+                .getResourceAsStream("/device.json");
+        InputStream ipv6JsonStream = SegmentRoutingDeviceConfigTest.class
+                .getResourceAsStream("/device-ipv6.json");
+        InputStream pairJsonStream = SegmentRoutingDeviceConfigTest.class
+                .getResourceAsStream("/device-pair.json");
+        InputStream invalidJsonStream = SegmentRoutingDeviceConfigTest.class
+                .getResourceAsStream("/device-invalid.json");
+
+        adjacencySids1 = new HashMap<>();
+        Set<Integer> ports1 = new HashSet<>();
+        ports1.add(2);
+        ports1.add(3);
+        adjacencySids1.put(100, ports1);
+        Set<Integer> ports2 = new HashSet<>();
+        ports2.add(4);
+        ports2.add(5);
+        adjacencySids1.put(200, ports2);
+
+        adjacencySids2 = new HashMap<>();
+        Set<Integer> ports3 = new HashSet<>();
+        ports3.add(6);
+        adjacencySids2.put(300, ports3);
+
+        DeviceId subject = DeviceId.deviceId("of:0000000000000001");
+        String key = "segmentrouting";
+        ObjectMapper mapper = new ObjectMapper();
+        JsonNode jsonNode = mapper.readTree(jsonStream);
+        JsonNode ipv6JsonNode = mapper.readTree(ipv6JsonStream);
+        JsonNode pairJsonNode = mapper.readTree(pairJsonStream);
+        JsonNode invalidJsonNode = mapper.readTree(invalidJsonStream);
+        ConfigApplyDelegate delegate = new MockDelegate();
+
+        config = new SegmentRoutingDeviceConfig();
+        config.init(subject, key, jsonNode, mapper, delegate);
+
+        ipv6Config = new SegmentRoutingDeviceConfig();
+        ipv6Config.init(subject, key, ipv6JsonNode, mapper, delegate);
+
+        pairConfig = new SegmentRoutingDeviceConfig();
+        pairConfig.init(subject, key, pairJsonNode, mapper, delegate);
+
+        invalidConfig = new SegmentRoutingDeviceConfig();
+        invalidConfig.init(subject, key, invalidJsonNode, mapper, delegate);
+    }
+
+    @Test
+    public void testIsValid() {
+        assertTrue(config.isValid());
+        assertTrue(ipv6Config.isValid());
+        assertTrue(pairConfig.isValid());
+        assertFalse(invalidConfig.isValid());
+    }
+
+    @Test
+    public void testName() throws Exception {
+        assertTrue(config.name().isPresent());
+        assertThat(config.name().get(), is("Leaf-R1"));
+    }
+
+    @Test
+    public void testSetName() throws Exception {
+        config.setName("Spine-R1");
+        assertTrue(config.name().isPresent());
+        assertThat(config.name().get(), is("Spine-R1"));
+    }
+
+    @Test
+    public void testRouterIp() throws Exception {
+        assertThat(config.routerIpv4(), is(IpAddress.valueOf("10.0.1.254")));
+        assertThat(ipv6Config.routerIpv4(), is(IpAddress.valueOf("10.0.1.254")));
+        assertThat(ipv6Config.routerIpv6(), is(IpAddress.valueOf("2000::c0a8:0101")));
+    }
+
+    @Test
+    public void testSetRouterIp() throws Exception {
+        config.setRouterIpv4("10.0.2.254");
+        assertThat(config.routerIpv4(), is(IpAddress.valueOf("10.0.2.254")));
+        ipv6Config.setRouterIpv4("10.0.2.254");
+        assertThat(ipv6Config.routerIpv4(), is(IpAddress.valueOf("10.0.2.254")));
+        ipv6Config.setRouterIpv6("2000::c0a9:0101");
+        assertThat(ipv6Config.routerIpv6(), is(IpAddress.valueOf("2000::c0a9:0101")));
+    }
+
+    @Test
+    public void testRouterMac() throws Exception {
+        assertThat(config.routerMac(), is(MacAddress.valueOf("00:00:00:00:01:80")));
+    }
+
+    @Test
+    public void testSetRouterMac() throws Exception {
+        config.setRouterMac("00:00:00:00:02:80");
+        assertThat(config.routerMac(), is(MacAddress.valueOf("00:00:00:00:02:80")));
+    }
+
+    @Test
+    public void testNodeSid() throws Exception {
+        assertThat(config.nodeSidIPv4(), is(101));
+        assertThat(ipv6Config.nodeSidIPv4(), is(101));
+        assertThat(ipv6Config.nodeSidIPv6(), is(111));
+    }
+
+    @Test
+    public void testSetNodeSid() throws Exception {
+        config.setNodeSidIPv4(200);
+        assertThat(config.nodeSidIPv4(), is(200));
+        ipv6Config.setNodeSidIPv4(200);
+        assertThat(ipv6Config.nodeSidIPv4(), is(200));
+        ipv6Config.setNodeSidIPv6(201);
+        assertThat(ipv6Config.nodeSidIPv6(), is(201));
+    }
+
+    @Test
+    public void testIsEdgeRouter() throws Exception {
+        assertThat(config.isEdgeRouter(), is(true));
+    }
+
+    @Test
+    public void testSetIsEdgeRouter() throws Exception {
+        config.setIsEdgeRouter(false);
+        assertThat(config.isEdgeRouter(), is(false));
+    }
+
+    @Test
+    public void testAdjacencySids() throws Exception {
+        assertThat(config.adjacencySids(), is(adjacencySids1));
+    }
+
+    @Test
+    public void testSetAdjacencySids() throws Exception {
+        config.setAdjacencySids(adjacencySids2);
+        assertThat(config.adjacencySids(), is(adjacencySids2));
+    }
+
+    @Test
+    public void testPairDeviceId() throws Exception {
+        assertNull(config.pairDeviceId());
+        assertNull(ipv6Config.pairDeviceId());
+        assertThat(pairConfig.pairDeviceId(), is(PAIR_DEVICE_ID));
+    }
+
+    @Test
+    public void testSetPairDeviceId() throws Exception {
+        config.setPairDeviceId(PAIR_DEVICE_ID);
+        assertThat(config.pairDeviceId(), is(PAIR_DEVICE_ID));
+    }
+
+    @Test
+    public void testPairLocalPort() throws Exception {
+        assertNull(config.pairLocalPort());
+        assertNull(ipv6Config.pairLocalPort());
+        assertThat(pairConfig.pairLocalPort(), is(PAIR_LOCAL_PORT));
+    }
+
+    @Test
+    public void testSetPairLocalPort() throws Exception {
+        config.setPairLocalPort(PAIR_LOCAL_PORT);
+        assertThat(config.pairLocalPort(), is(PAIR_LOCAL_PORT));
+    }
+
+    private class MockDelegate implements ConfigApplyDelegate {
+        @Override
+        public void onApply(Config configFile) {
+        }
+    }
+}
diff --git a/api/src/test/resources/app-ecmp.json b/api/src/test/resources/app-ecmp.json
new file mode 100644
index 0000000..e94b1c5
--- /dev/null
+++ b/api/src/test/resources/app-ecmp.json
@@ -0,0 +1,19 @@
+{
+  "vRouterMacs" : [
+      "00:00:00:00:00:01",
+      "00:00:00:00:00:02"
+  ],
+  "suppressSubnet" : [
+      "of:1/1",
+      "of:1/2"
+  ],
+  "suppressHostByPort" : [
+      "of:1/1",
+      "of:1/2"
+  ],
+  "suppressHostByProvider" : [
+      "org.onosproject.provider.host",
+      "org.onosproject.netcfghost"
+  ],
+  "MPLS-ECMP" : true
+}
diff --git a/api/src/test/resources/app-invalid.json b/api/src/test/resources/app-invalid.json
new file mode 100644
index 0000000..01508f8
--- /dev/null
+++ b/api/src/test/resources/app-invalid.json
@@ -0,0 +1,15 @@
+{
+  "vRouterMacs" : [
+      "00:00:00:00:00:01",
+      "00:00:00:00:00:02"
+  ],
+  "suppressSubnet" : [
+      "of:1/1",
+      "of:1/2"
+  ],
+  "suppressHostByPort" : [
+      "of:1/1",
+      "wrongPort"
+  ],
+  "suppressHostByProvider" : []
+}
diff --git a/api/src/test/resources/app.json b/api/src/test/resources/app.json
new file mode 100644
index 0000000..2e59b0d
--- /dev/null
+++ b/api/src/test/resources/app.json
@@ -0,0 +1,21 @@
+{
+  "vRouterMacs" : [
+      "00:00:00:00:00:01",
+      "00:00:00:00:00:02"
+  ],
+  "suppressSubnet" : [
+      "of:1/1",
+      "of:1/2"
+  ],
+  "suppressHostByPort" : [
+      "of:1/1",
+      "of:1/2"
+  ],
+  "suppressHostByProvider" : [
+      "org.onosproject.provider.host",
+      "org.onosproject.netcfghost"
+  ],
+  "blackholeIps": [
+    "10.0.0.0/8"
+  ]
+}
diff --git a/api/src/test/resources/blocked-ports-alt.json b/api/src/test/resources/blocked-ports-alt.json
new file mode 100644
index 0000000..3d8749e
--- /dev/null
+++ b/api/src/test/resources/blocked-ports-alt.json
@@ -0,0 +1,5 @@
+{
+  "of:0000000000000001": ["1-9"],
+  "of:0000000000000003": ["7"],
+  "of:0000000000000004": ["1"]
+}
diff --git a/api/src/test/resources/blocked-ports.json b/api/src/test/resources/blocked-ports.json
new file mode 100644
index 0000000..2543f3d
--- /dev/null
+++ b/api/src/test/resources/blocked-ports.json
@@ -0,0 +1,5 @@
+{
+  "of:0000000000000001": ["1-4", "7-9"],
+  "of:0000000000000003": ["7-9"],
+  "of:0000000000000004": ["1", "5", "9"]
+}
diff --git a/api/src/test/resources/device-invalid.json b/api/src/test/resources/device-invalid.json
new file mode 100644
index 0000000..dfcbdb8
--- /dev/null
+++ b/api/src/test/resources/device-invalid.json
@@ -0,0 +1,12 @@
+{
+  "name" : "Leaf-R1",
+  "ipv4NodeSid" : 101,
+  "ipv4Loopback" : "10.0.1.254",
+  "routerMac" : "00:00:00:00:01:80",
+  "isEdgeRouter" : true,
+  "adjacencySids" : [
+    { "adjSid" : 100, "ports" : [2, 3] },
+    { "adjSid" : 200, "ports" : [4, 5] }
+  ],
+  "pairDeviceId" : "of:123456789ABCDEF0"
+}
diff --git a/api/src/test/resources/device-ipv6.json b/api/src/test/resources/device-ipv6.json
new file mode 100644
index 0000000..9832f8c
--- /dev/null
+++ b/api/src/test/resources/device-ipv6.json
@@ -0,0 +1,13 @@
+{
+  "name" : "Leaf-R1",
+  "ipv4NodeSid" : 101,
+  "ipv4Loopback" : "10.0.1.254",
+  "ipv6NodeSid" : 111,
+  "ipv6Loopback" : "2000::c0a8:0101",
+  "routerMac" : "00:00:00:00:01:80",
+  "isEdgeRouter" : true,
+  "adjacencySids" : [
+    { "adjSid" : 100, "ports" : [2, 3] },
+    { "adjSid" : 200, "ports" : [4, 5] }
+  ]
+}
diff --git a/api/src/test/resources/device-pair.json b/api/src/test/resources/device-pair.json
new file mode 100644
index 0000000..c699ff5
--- /dev/null
+++ b/api/src/test/resources/device-pair.json
@@ -0,0 +1,13 @@
+{
+  "name" : "Leaf-R1",
+  "ipv4NodeSid" : 101,
+  "ipv4Loopback" : "10.0.1.254",
+  "routerMac" : "00:00:00:00:01:80",
+  "isEdgeRouter" : true,
+  "adjacencySids" : [
+    { "adjSid" : 100, "ports" : [2, 3] },
+    { "adjSid" : 200, "ports" : [4, 5] }
+  ],
+  "pairDeviceId" : "of:123456789ABCDEF0",
+  "pairLocalPort" : "10"
+}
diff --git a/api/src/test/resources/device.json b/api/src/test/resources/device.json
new file mode 100644
index 0000000..83aec6e
--- /dev/null
+++ b/api/src/test/resources/device.json
@@ -0,0 +1,11 @@
+{
+  "name" : "Leaf-R1",
+  "ipv4NodeSid" : 101,
+  "ipv4Loopback" : "10.0.1.254",
+  "routerMac" : "00:00:00:00:01:80",
+  "isEdgeRouter" : true,
+  "adjacencySids" : [
+    { "adjSid" : 100, "ports" : [2, 3] },
+    { "adjSid" : 200, "ports" : [4, 5] }
+  ]
+}