New cord-tester cross connect app to configure the leaf switch for cord-tester.
This is required when cord-tester is running on the head node on the PODD.
And compute node access is needed to test vcpes from inside the podd head node.
For now, we don't enable the configuration of the app as one cannot overwrite default xconnect flows.
Like eg: 222 which would be already configured.
In this case, we would just need new entries in olt config or use a new subscriber vcpe list
to open up for cord-tester vsg testing.

Change-Id: Ie96c3eba79aded235e67f05d806722abe6024ffa
diff --git a/src/test/apps/xconnect-1.0-SNAPSHOT.oar b/src/test/apps/xconnect-1.0-SNAPSHOT.oar
new file mode 100644
index 0000000..4089ed7
--- /dev/null
+++ b/src/test/apps/xconnect-1.0-SNAPSHOT.oar
Binary files differ
diff --git a/src/test/apps/xconnect/pom.xml b/src/test/apps/xconnect/pom.xml
new file mode 100644
index 0000000..ed8c345
--- /dev/null
+++ b/src/test/apps/xconnect/pom.xml
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2017 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.
+  -->
+<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>
+
+    <groupId>org.ciena.xconnect</groupId>
+    <artifactId>xconnect</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+
+    <description>ONOS OSGi bundle archetype</description>
+    <url>http://onosproject.org</url>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <onos.version>1.8.0</onos.version>
+
+        <onos.app.name>org.ciena.xconnect</onos.app.name>
+        <onos.app.title>ciena xconnect</onos.app.title>
+        <onos.app.origin>ciena</onos.app.origin>
+        <onos.app.category>default</onos.app.category>
+        <onos.app.url>http://onosproject.org</onos.app.url>
+        <onos.app.readme>ONOS OSGi bundle archetype.</onos.app.readme>
+
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-api</artifactId>
+            <version>${onos.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-osgi</artifactId>
+            <version>${onos.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-core-serializers</artifactId>
+            <version>${onos.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-api</artifactId>
+            <version>${onos.version}</version>
+            <scope>test</scope>
+            <classifier>tests</classifier>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.scr.annotations</artifactId>
+            <version>1.9.12</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <version>3.0.1</version>
+                <extensions>true</extensions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>2.5.1</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+                <version>1.21.0</version>
+                <executions>
+                    <execution>
+                        <id>generate-scr-srcdescriptor</id>
+                        <goals>
+                            <goal>scr</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <supportedProjectTypes>
+                        <supportedProjectType>bundle</supportedProjectType>
+                        <supportedProjectType>war</supportedProjectType>
+                    </supportedProjectTypes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.onosproject</groupId>
+                <artifactId>onos-maven-plugin</artifactId>
+                <version>1.10</version>
+                <executions>
+                    <execution>
+                        <id>cfg</id>
+                        <phase>generate-resources</phase>
+                        <goals>
+                            <goal>cfg</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>swagger</id>
+                        <phase>generate-sources</phase>
+                        <goals>
+                            <goal>swagger</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>app</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>app</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/src/test/apps/xconnect/src/main/java/org/ciena/xconnect/AppComponent.java b/src/test/apps/xconnect/src/main/java/org/ciena/xconnect/AppComponent.java
new file mode 100644
index 0000000..f70179f
--- /dev/null
+++ b/src/test/apps/xconnect/src/main/java/org/ciena/xconnect/AppComponent.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright 2017-present 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.ciena.xconnect;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.basics.SubjectFactories;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.ApplicationId;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+import org.apache.felix.scr.annotations.*;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flowobjective.*;
+import org.onosproject.net.flow.criteria.Criteria;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.net.config.ConfigFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Skeletal ONOS application component.
+ */
+@Component(immediate = true)
+public class AppComponent {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private static final String NOT_MASTER = "Not master controller";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    public FlowObjectiveService flowObjectiveService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MastershipService mastershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected StorageService storageService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    public NetworkConfigRegistry networkConfig;
+
+    private InternalNetworkConfigListener configListener =
+            new InternalNetworkConfigListener();
+
+    private ApplicationId appId;
+
+    private KryoNamespace.Builder xConnectKryo;
+
+    private ConsistentMap<XConnectStoreKey, NextObjective> xConnectNextObjStore;
+
+    private static final Class<XConnectTestConfig> XCONNECT_TEST_CONFIG_CLASS = XConnectTestConfig.class;
+
+    private ConfigFactory<ApplicationId, XConnectTestConfig> xconnectTestConfigFactory =
+            new ConfigFactory<ApplicationId, XConnectTestConfig>(
+                    SubjectFactories.APP_SUBJECT_FACTORY, XCONNECT_TEST_CONFIG_CLASS, "xconnectTestConfig") {
+                @Override
+                public XConnectTestConfig createConfig() {
+                    return new XConnectTestConfig();
+                }
+            };
+
+    @Activate
+    protected void activate() {
+        log.info("Started");
+        appId = coreService.registerApplication("org.ciena.xconnect");
+
+        xConnectKryo = new KryoNamespace.Builder()
+                .register(KryoNamespaces.API)
+                .register(XConnectStoreKey.class)
+                .register(NextObjContext.class);
+
+        xConnectNextObjStore = storageService
+                .<XConnectStoreKey, NextObjective>consistentMapBuilder()
+                .withName("cordtester-xconnect-nextobj-store")
+                .withSerializer(Serializer.using(xConnectKryo.build()))
+                .build();
+
+        networkConfig.addListener(configListener);
+        networkConfig.registerConfigFactory(xconnectTestConfigFactory);
+
+        XConnectTestConfig config = networkConfig.getConfig(appId, XConnectTestConfig.class);
+
+        if (config != null) {
+            config.getXconnects().forEach(key -> {
+                    populateXConnect(key, config.getPorts(key));
+                });
+        }
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        log.info("Stopped");
+        networkConfig.removeListener(configListener);
+        XConnectTestConfig config = networkConfig.getConfig(appId, XConnectTestConfig.class);
+        //remove flows on app deactivate
+        if (config != null) {
+            config.getXconnects().forEach(key -> {
+                    revokeXConnect(key, config.getPorts(key));
+                });
+        }
+        networkConfig.unregisterConfigFactory(xconnectTestConfigFactory);
+    }
+
+    /**
+     * Populates XConnect groups and flows for given key.
+     *
+     * @param key XConnect key
+     * @param ports a set of ports to be cross-connected
+     */
+    private void populateXConnect(XConnectStoreKey key, Set<PortNumber> ports) {
+        if (!mastershipService.isLocalMaster(key.deviceId())) {
+            log.info("Abort populating XConnect {}: {}", key, NOT_MASTER);
+            return;
+        }
+        populateFilter(key, ports);
+        populateFwd(key, populateNext(key, ports));
+    }
+
+    private void populateFilter(XConnectStoreKey key, Set<PortNumber> ports) {
+        ports.forEach(port -> {
+            FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port);
+            ObjectiveContext context = new DefaultObjectiveContext(
+                    (objective) -> log.debug("XConnect FilterObj for {} on port {} populated",
+                            key, port),
+                    (objective, error) ->
+                            log.warn("Failed to populate XConnect FilterObj for {} on port {}: {}",
+                                    key, port, error));
+            flowObjectiveService.filter(key.deviceId(), filtObjBuilder.add(context));
+        });
+    }
+
+    private FilteringObjective.Builder filterObjBuilder(XConnectStoreKey key, PortNumber port) {
+        FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
+        fob.withKey(Criteria.matchInPort(port))
+                .addCondition(Criteria.matchVlanId(key.vlanId()))
+                .addCondition(Criteria.matchEthDst(MacAddress.NONE))
+                .withPriority(1234);
+        return fob.permit().fromApp(appId);
+    }
+
+    private NextObjective populateNext(XConnectStoreKey key, Set<PortNumber> ports) {
+        NextObjective nextObj = null;
+        if (xConnectNextObjStore.containsKey(key)) {
+            nextObj = xConnectNextObjStore.get(key).value();
+            log.debug("NextObj for {} found, id={}", key, nextObj.id());
+        } else {
+            NextObjective.Builder nextObjBuilder = nextObjBuilder(key, ports);
+            ObjectiveContext nextContext = new NextObjContext(Objective.Operation.ADD, key);
+            nextObj = nextObjBuilder.add(nextContext);
+            flowObjectiveService.next(key.deviceId(), nextObj);
+            xConnectNextObjStore.put(key, nextObj);
+            log.info("NextObj for {} not found. Creating new NextObj with id={}", key, nextObj.id());
+        }
+        return nextObj;
+    }
+
+    private NextObjective.Builder nextObjBuilder(XConnectStoreKey key, Set<PortNumber> ports) {
+        int nextId = flowObjectiveService.allocateNextId();
+        TrafficSelector metadata =
+                DefaultTrafficSelector.builder().matchVlanId(key.vlanId()).build();
+        NextObjective.Builder nextObjBuilder = DefaultNextObjective
+                .builder().withId(nextId)
+                .withType(NextObjective.Type.BROADCAST).fromApp(appId)
+                .withMeta(metadata);
+        ports.forEach(port -> {
+            TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+            tBuilder.setOutput(port);
+            nextObjBuilder.addTreatment(tBuilder.build());
+        });
+        return nextObjBuilder;
+    }
+
+    private void populateFwd(XConnectStoreKey key, NextObjective nextObj) {
+        ForwardingObjective.Builder fwdObjBuilder = fwdObjBuilder(key, nextObj.id());
+        ObjectiveContext fwdContext = new DefaultObjectiveContext(
+                (objective) -> log.debug("XConnect FwdObj for {} populated", key),
+                (objective, error) ->
+                        log.warn("Failed to populate XConnect FwdObj for {}: {}", key, error));
+        flowObjectiveService.forward(key.deviceId(), fwdObjBuilder.add(fwdContext));
+    }
+
+    private ForwardingObjective.Builder fwdObjBuilder(XConnectStoreKey key, int nextId) {
+        /*
+         * Driver should treat objectives with MacAddress.NONE and !VlanId.NONE
+         * as the VLAN cross-connect broadcast rules
+         */
+        TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
+        sbuilder.matchVlanId(key.vlanId());
+        sbuilder.matchEthDst(MacAddress.NONE);
+
+        ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
+        fob.withFlag(ForwardingObjective.Flag.SPECIFIC)
+                .withSelector(sbuilder.build())
+                .nextStep(nextId)
+                .withPriority(32768)
+                .fromApp(appId)
+                .makePermanent();
+        return fob;
+    }
+
+    /**
+     * Processes Segment Routing App Config added event.
+     *
+     * @param event network config added event
+     */
+    protected void processXConnectConfigAdded(NetworkConfigEvent event) {
+        log.info("Processing XConnect CONFIG_ADDED");
+        XConnectTestConfig config = (XConnectTestConfig) event.config().get();
+        config.getXconnects().forEach(key -> {
+            populateXConnect(key, config.getPorts(key));
+        });
+    }
+
+    /**
+     * Processes Segment Routing App Config removed event.
+     *
+     * @param event network config removed event
+     */
+    protected void processXConnectConfigRemoved(NetworkConfigEvent event) {
+        log.info("Processing XConnect CONFIG_REMOVED");
+        XConnectTestConfig prevConfig = (XConnectTestConfig) event.prevConfig().get();
+        prevConfig.getXconnects().forEach(key -> {
+            revokeXConnect(key, prevConfig.getPorts(key));
+        });
+    }
+
+    /**
+     * Revokes filtering objectives for given XConnect.
+     *
+     * @param key XConnect store key
+     * @param ports XConnect ports
+     */
+    private void revokeFilter(XConnectStoreKey key, Set<PortNumber> ports) {
+        ports.forEach(port -> {
+            FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port);
+            ObjectiveContext context = new DefaultObjectiveContext(
+                    (objective) -> log.debug("XConnect FilterObj for {} on port {} revoked",
+                            key, port),
+                    (objective, error) ->
+                            log.warn("Failed to revoke XConnect FilterObj for {} on port {}: {}",
+                                    key, port, error));
+            flowObjectiveService.filter(key.deviceId(), filtObjBuilder.remove(context));
+        });
+    }
+
+    /**
+     * Revokes next objectives for given XConnect.
+     *
+     * @param key XConnect store key
+     * @param nextObj next objective
+     * @param nextFuture completable future for this next objective operation
+     */
+    private void revokeNext(XConnectStoreKey key, NextObjective nextObj,
+            CompletableFuture<ObjectiveError> nextFuture) {
+        ObjectiveContext context = new ObjectiveContext() {
+            @Override
+            public void onSuccess(Objective objective) {
+                log.debug("Previous NextObj for {} removed", key);
+                if (nextFuture != null) {
+                    nextFuture.complete(null);
+                }
+            }
+
+            @Override
+            public void onError(Objective objective, ObjectiveError error) {
+                log.warn("Failed to remove previous NextObj for {}: {}", key, error);
+                if (nextFuture != null) {
+                    nextFuture.complete(error);
+                }
+            }
+        };
+        flowObjectiveService.next(key.deviceId(),
+                                  (NextObjective) nextObj.copy().remove(context));
+        xConnectNextObjStore.remove(key);
+    }
+
+    /**
+     * Revokes forwarding objectives for given XConnect.
+     *
+     * @param key XConnect store key
+     * @param nextObj next objective
+     * @param fwdFuture completable future for this forwarding objective operation
+     */
+    private void revokeFwd(XConnectStoreKey key, NextObjective nextObj,
+            CompletableFuture<ObjectiveError> fwdFuture) {
+        ForwardingObjective.Builder fwdObjBuilder = fwdObjBuilder(key, nextObj.id());
+        ObjectiveContext context = new ObjectiveContext() {
+            @Override
+            public void onSuccess(Objective objective) {
+                log.debug("Previous FwdObj for {} removed", key);
+                if (fwdFuture != null) {
+                    fwdFuture.complete(null);
+                }
+            }
+
+            @Override
+            public void onError(Objective objective, ObjectiveError error) {
+                log.warn("Failed to remove previous FwdObj for {}: {}", key, error);
+                if (fwdFuture != null) {
+                    fwdFuture.complete(error);
+                }
+            }
+        };
+        flowObjectiveService
+            .forward(key.deviceId(), fwdObjBuilder.remove(context));
+    }
+
+    private void revokeXConnect(XConnectStoreKey key, Set<PortNumber> ports) {
+        if (!mastershipService.isLocalMaster(key.deviceId())) {
+            log.info("Abort populating XConnect {}: {}", key, NOT_MASTER);
+            return;
+        }
+        revokeFilter(key, ports);
+        if (xConnectNextObjStore.containsKey(key)) {
+            NextObjective nextObj = xConnectNextObjStore.get(key).value();
+            revokeFwd(key, nextObj, null);
+            revokeNext(key, nextObj, null);
+        } else {
+            log.warn("NextObj for {} does not exist in the store.", key);
+        }
+    }
+
+    private final class NextObjContext implements ObjectiveContext {
+        Objective.Operation op;
+        XConnectStoreKey key;
+
+        private NextObjContext(Objective.Operation op, XConnectStoreKey key) {
+            this.op = op;
+            this.key = key;
+        }
+
+        @Override
+        public void onSuccess(Objective objective) {
+            log.debug("XConnect NextObj for {} {}ED", key, op);
+        }
+
+        @Override
+        public void onError(Objective objective, ObjectiveError error) {
+            log.warn("Failed to {} XConnect NextObj for {}: {}", op, key, error);
+        }
+    }
+
+    private class InternalNetworkConfigListener implements NetworkConfigListener {
+
+        @Override
+        public void event(NetworkConfigEvent event) {
+            if (event.configClass().equals(XCONNECT_TEST_CONFIG_CLASS)) {
+                switch (event.type()) {
+                    case CONFIG_ADDED:
+                        processXConnectConfigAdded(event);
+                        break;
+                    case CONFIG_UPDATED:
+                        log.info("CONFIG UPDATED event is unhandled");
+                        break;
+                    case CONFIG_REMOVED:
+                        processXConnectConfigRemoved(event);
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+    }
+}
diff --git a/src/test/apps/xconnect/src/main/java/org/ciena/xconnect/XConnectStoreKey.java b/src/test/apps/xconnect/src/main/java/org/ciena/xconnect/XConnectStoreKey.java
new file mode 100644
index 0000000..347ca37
--- /dev/null
+++ b/src/test/apps/xconnect/src/main/java/org/ciena/xconnect/XConnectStoreKey.java
@@ -0,0 +1,85 @@
+package org.ciena.xconnect;
+/*
+ * Copyright 2016-present 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.storekey;
+
+import org.onlab.packet.VlanId;
+import org.onosproject.net.DeviceId;
+
+import java.util.Objects;
+
+/**
+ * Key of VLAN cross-connect next objective store.
+ */
+public class XConnectStoreKey {
+    private final DeviceId deviceId;
+    private final VlanId vlanId;
+
+    /**
+     * Constructs the key of cross-connect next objective store.
+     *
+     * @param deviceId device ID of the VLAN cross-connection
+     * @param vlanId VLAN ID of the VLAN cross-connection
+     */
+    public XConnectStoreKey(DeviceId deviceId, VlanId vlanId) {
+        this.deviceId = deviceId;
+        this.vlanId = vlanId;
+    }
+
+    /**
+     * Returns the device ID of this key.
+     *
+     * @return device ID
+     */
+    public DeviceId deviceId() {
+        return this.deviceId;
+    }
+
+    /**
+     * Returns the VLAN ID of this key.
+     *
+     * @return VLAN ID
+     */
+    public VlanId vlanId() {
+        return this.vlanId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof XConnectStoreKey)) {
+            return false;
+        }
+        XConnectStoreKey that =
+                (XConnectStoreKey) o;
+        return (Objects.equals(this.deviceId, that.deviceId) &&
+                Objects.equals(this.vlanId, that.vlanId));
+    }
+
+    // The list of neighbor ids and label are used for comparison.
+    @Override
+    public int hashCode() {
+        return Objects.hash(deviceId, vlanId);
+    }
+
+    @Override
+    public String toString() {
+        return "Device: " + deviceId + " VlanId: " + vlanId;
+    }
+}
diff --git a/src/test/apps/xconnect/src/main/java/org/ciena/xconnect/XConnectTestConfig.java b/src/test/apps/xconnect/src/main/java/org/ciena/xconnect/XConnectTestConfig.java
new file mode 100644
index 0000000..86b1226
--- /dev/null
+++ b/src/test/apps/xconnect/src/main/java/org/ciena/xconnect/XConnectTestConfig.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2016 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.ciena.xconnect;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.Config;
+
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Configuration object for cross-connect.
+ */
+public class XConnectTestConfig extends Config<ApplicationId> {
+
+    private static final String VLAN = "vlan";
+    private static final String PORTS = "ports";
+    private static final String UNEXPECTED_FIELD_NAME = "Unexpected field name";
+
+    @Override
+    public boolean isValid() {
+        try {
+            getXconnects().forEach(this::getPorts);
+        } catch (IllegalArgumentException e) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns all xconnect keys.
+     *
+     * @return all keys (device/vlan pairs)
+     * @throws IllegalArgumentException if wrong format
+     */
+    public Set<XConnectStoreKey> getXconnects() {
+        ImmutableSet.Builder<XConnectStoreKey> builder = ImmutableSet.builder();
+        object.fields().forEachRemaining(entry -> {
+            DeviceId deviceId = DeviceId.deviceId(entry.getKey());
+            builder.addAll(getXconnects(deviceId));
+        });
+        return builder.build();
+    }
+
+    /**
+     * Returns xconnect keys of given device.
+     *
+     * @param deviceId ID of the device from which we want to get XConnect info
+     * @return xconnect keys (device/vlan pairs) of given device
+     * @throws IllegalArgumentException if wrong format
+     */
+    public Set<XConnectStoreKey> getXconnects(DeviceId deviceId) {
+        ImmutableSet.Builder<XConnectStoreKey> builder = ImmutableSet.builder();
+        JsonNode vlanPortPair = object.get(deviceId.toString());
+        if (vlanPortPair != null) {
+            vlanPortPair.forEach(jsonNode -> {
+                if (!hasOnlyFields((ObjectNode) jsonNode, VLAN, PORTS)) {
+                    throw new IllegalArgumentException(UNEXPECTED_FIELD_NAME);
+                }
+                VlanId vlanId = VlanId.vlanId((short) jsonNode.get(VLAN).asInt());
+                builder.add(new XConnectStoreKey(deviceId, vlanId));
+            });
+        }
+        return builder.build();
+    }
+
+    /**
+     * Returns ports of given xconnect key.
+     *
+     * @param xconnect xconnect key
+     * @return set of two ports associated with given xconnect key
+     * @throws IllegalArgumentException if wrong format
+     */
+    public Set<PortNumber> getPorts(XConnectStoreKey xconnect) {
+        ImmutableSet.Builder<PortNumber> builder = ImmutableSet.builder();
+        object.get(xconnect.deviceId().toString()).forEach(vlanPortsPair -> {
+            if (xconnect.vlanId().toShort() == vlanPortsPair.get(VLAN).asInt()) {
+                int portCount = vlanPortsPair.get(PORTS).size();
+                checkArgument(portCount == 2,
+                        "Expect 2 ports but found " + portCount + " on " + xconnect);
+                vlanPortsPair.get(PORTS).forEach(portNode -> {
+                    builder.add(PortNumber.portNumber(portNode.asInt()));
+                });
+            }
+        });
+        return builder.build();
+    }
+}
diff --git a/src/test/apps/xconnect/src/test/java/org/ciena/xconnect/AppComponentTest.java b/src/test/apps/xconnect/src/test/java/org/ciena/xconnect/AppComponentTest.java
new file mode 100644
index 0000000..9c3b874
--- /dev/null
+++ b/src/test/apps/xconnect/src/test/java/org/ciena/xconnect/AppComponentTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017-present 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.ciena.xconnect;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Set of tests of the ONOS application component.
+ */
+public class AppComponentTest {
+
+    private AppComponent component;
+
+    @Before
+    public void setUp() {
+        component = new AppComponent();
+        component.activate();
+
+    }
+
+    @After
+    public void tearDown() {
+        component.deactivate();
+    }
+
+    @Test
+    public void basics() {
+
+    }
+
+}
diff --git a/src/test/apps/xconnect/xconnect.iml b/src/test/apps/xconnect/xconnect.iml
new file mode 100644
index 0000000..0ecc1bf
--- /dev/null
+++ b/src/test/apps/xconnect/xconnect.iml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8" inherit-compiler-output="false">
+    <output url="file://$MODULE_DIR$/target/classes" />
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+      <excludeFolder url="file://$MODULE_DIR$/target" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: org.onosproject:onos-api:1.8.0-SNAPSHOT" level="project" />
+    <orderEntry type="library" name="Maven: joda-time:joda-time:2.9.3" level="project" />
+    <orderEntry type="library" name="Maven: commons-configuration:commons-configuration:1.10" level="project" />
+    <orderEntry type="library" name="Maven: commons-lang:commons-lang:2.6" level="project" />
+    <orderEntry type="library" name="Maven: commons-logging:commons-logging:1.1.1" level="project" />
+    <orderEntry type="library" name="Maven: commons-collections:commons-collections:3.2.2" level="project" />
+    <orderEntry type="library" name="Maven: org.onosproject:onlab-rest:1.8.0-SNAPSHOT" level="project" />
+    <orderEntry type="library" name="Maven: javax.ws.rs:javax.ws.rs-api:2.0.1" level="project" />
+    <orderEntry type="library" name="Maven: com.google.guava:guava:19.0" level="project" />
+    <orderEntry type="library" name="Maven: org.onosproject:onlab-misc:1.8.0-SNAPSHOT" level="project" />
+    <orderEntry type="library" name="Maven: io.netty:netty:3.10.5.Final" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.4" level="project" />
+    <orderEntry type="library" name="Maven: com.eclipsesource.minimal-json:minimal-json:0.9.4" level="project" />
+    <orderEntry type="library" name="Maven: com.esotericsoftware:kryo:4.0.0" level="project" />
+    <orderEntry type="library" name="Maven: com.esotericsoftware:reflectasm:1.11.3" level="project" />
+    <orderEntry type="library" name="Maven: org.ow2.asm:asm:5.0.4" level="project" />
+    <orderEntry type="library" name="Maven: com.esotericsoftware:minlog:1.3.0" level="project" />
+    <orderEntry type="library" name="Maven: org.objenesis:objenesis:2.2" level="project" />
+    <orderEntry type="library" name="Maven: io.dropwizard.metrics:metrics-core:3.1.2" level="project" />
+    <orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.7" level="project" />
+    <orderEntry type="library" name="Maven: io.dropwizard.metrics:metrics-json:3.1.2" level="project" />
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.4.2" level="project" />
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.4.0" level="project" />
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.4.2" level="project" />
+    <orderEntry type="library" name="Maven: com.google.code.findbugs:jsr305:3.0.1" level="project" />
+    <orderEntry type="library" name="Maven: org.onosproject:onlab-osgi:1.8.0-SNAPSHOT" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: junit:junit:4.12" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-core:1.3" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.onosproject:onos-api:tests:1.8.0-SNAPSHOT" level="project" />
+    <orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.felix:org.apache.felix.scr.annotations:1.9.12" level="project" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/src/test/apps/xconnect/xconnect.json b/src/test/apps/xconnect/xconnect.json
new file mode 100644
index 0000000..b0321e6
--- /dev/null
+++ b/src/test/apps/xconnect/xconnect.json
@@ -0,0 +1,18 @@
+{
+    "apps":
+    {
+        "org.ciena.xconnect":
+        {
+            "xconnectTestConfig":
+            {
+                "of:00007a916740ea43":
+                [
+                    {
+                        "vlan": 555,
+                        "ports": [1, 2]
+                    }
+                ]
+            }
+        }
+    }
+}
diff --git a/src/test/vsg/vsgTest.py b/src/test/vsg/vsgTest.py
index 125397b..82eb147 100644
--- a/src/test/vsg/vsgTest.py
+++ b/src/test/vsg/vsgTest.py
@@ -50,6 +50,10 @@
     subscriber_map = {}
     restore_methods = []
     TIMEOUT=120
+    FABRIC_PORT_HEAD_NODE = 1
+    FABRIC_PORT_COMPUTE_NODE = 2
+    APP_NAME = 'org.ciena.xconnect'
+    APP_FILE = os.path.join(test_path, '..', 'apps/xconnect-1.0-SNAPSHOT.oar')
 
     @classmethod
     def getSubscriberCredentials(cls, subId):
@@ -133,6 +137,35 @@
         cls.restApiXos = restApiXos
 
     @classmethod
+    def closeVCPEAccess(cls, vcpes):
+        return
+        OnosCtrl.uninstall_app(cls.APP_NAME, onos_ip = cls.HEAD_NODE)
+
+    @classmethod
+    def openVCPEAccess(cls, vcpes):
+        """
+        This code works below to configure the leaf switch.
+        But it needs the olt_config.json to be modified to not overlap with existing/default vcpes.
+        That is to avoid overwriting the flows already provisioned for eg: for 222 vcpe.
+        (default and created on CiaB).
+        So returning for now with a no-op
+        """
+        return
+        OnosCtrl.install_app(cls.APP_FILE, onos_ip = cls.HEAD_NODE)
+        time.sleep(2)
+        s_tags = map(lambda vcpe: int(vcpe['s_tag']), vcpes)
+        devices = OnosCtrl.get_device_ids(controller = cls.HEAD_NODE)
+        device_config = {}
+        for device in devices:
+            device_config[device] = []
+            for s_tag in s_tags:
+                xconnect_config = {'vlan': s_tag, 'ports' : [ cls.FABRIC_PORT_HEAD_NODE, cls.FABRIC_PORT_COMPUTE_NODE ] }
+                device_config[device].append(xconnect_config)
+
+        cfg = { 'apps' : { 'org.ciena.xconnect' : { 'xconnectTestConfig' : device_config } } }
+        OnosCtrl.config(cfg, controller = cls.HEAD_NODE)
+
+    @classmethod
     def setUpClass(cls):
         cls.controllers = get_controllers()
         cls.controller = cls.controllers[0]
@@ -156,10 +189,14 @@
         cls.vcpe_dhcp_stag = vcpe_dhcp_stag
         VSGAccess.setUp()
         cls.setUpCordApi()
+        if cls.on_podd is True:
+            cls.openVCPEAccess(cls.vcpes_dhcp)
 
     @classmethod
     def tearDownClass(cls):
         VSGAccess.tearDown()
+        if cls.on_podd is True:
+            cls.closeVCPEAccess(cls.vcpes_dhcp)
 
     def cliEnter(self, controller = None):
         retries = 0