Add VLAN cross connect config for single switch fabric

Change-Id: Ib01f6cd0325225c9573343e1cd3a2c0f52df5b7c
diff --git a/local/ce-fabric/features.xml b/local/ce-fabric/features.xml
new file mode 100644
index 0000000..83ba135
--- /dev/null
+++ b/local/ce-fabric/features.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+  ~ 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.
+  -->
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}">
+    <feature name="${project.artifactId}" version="${project.version}"
+             description="${project.description}">
+        <bundle>mvn:com.google.code.gson/gson/2.6.2</bundle>
+    </feature>
+</features>
diff --git a/local/ce-central-office/pom.xml b/local/ce-fabric/pom.xml
similarity index 67%
rename from local/ce-central-office/pom.xml
rename to local/ce-fabric/pom.xml
index 1ad4a4a..0e3bb75 100644
--- a/local/ce-central-office/pom.xml
+++ b/local/ce-fabric/pom.xml
@@ -25,16 +25,18 @@
         <version>1.0.0-SNAPSHOT</version>
     </parent>
 
-    <artifactId>co</artifactId>
+    <artifactId>fabric</artifactId>
     <packaging>bundle</packaging>
 
     <description>CORD application for Carrier Ethernet Service</description>
 
     <properties>
-        <onos.app.name>org.opencord.ce.local.co</onos.app.name>
+        <onos.app.name>org.opencord.ce.local.fabric</onos.app.name>
         <onos.version>1.10.6</onos.version>
-        <onos.app.title>ECORD Central-Office Fabric config</onos.app.title>
+        <onos.app.title>E-CORD Central Office fabric config</onos.app.title>
         <onos.app.url>http://opencord.org</onos.app.url>
+        <onos.app.requires>org.opencord.ce.local.bigswitch</onos.app.requires>
+        <onos.app.requires>org.opencord.ce.local.channel.http</onos.app.requires>
     </properties>
 
     <build>
@@ -56,10 +58,38 @@
 
     <dependencies>
         <dependency>
+            <groupId>org.opencord.ce</groupId>
+            <artifactId>bigswitch</artifactId>
+            <version>${parent.version}</version>
+        </dependency>
+
+        <dependency>
             <groupId>com.google.code.gson</groupId>
             <artifactId>gson</artifactId>
             <version>2.6.2</version>
         </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-api</artifactId>
+            <version>${onos.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-osgi</artifactId>
+            <version>${onos.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.media</groupId>
+            <artifactId>jersey-media-json-jackson</artifactId>
+            <version>2.25.1</version>
+        </dependency>
     </dependencies>
 
     <repositories>
diff --git a/local/ce-fabric/src/main/java/org/opencord/ce/local/fabric/CarrierEthernetFabricConfig.java b/local/ce-fabric/src/main/java/org/opencord/ce/local/fabric/CarrierEthernetFabricConfig.java
new file mode 100644
index 0000000..2d8a9c1
--- /dev/null
+++ b/local/ce-fabric/src/main/java/org/opencord/ce/local/fabric/CarrierEthernetFabricConfig.java
@@ -0,0 +1,78 @@
+/*
+ * 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.opencord.ce.local.fabric;
+
+import org.onlab.packet.IpAddress;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.config.Config;
+
+/**
+ * Configuration for single switch CORD fabric manager for Carrier Ethernet services.
+ */
+public class CarrierEthernetFabricConfig extends Config<ApplicationId> {
+    private static final int DEFAULT_PORT = 8181;
+    private static final String DEFAULT_USERNAME = "onos";
+    private static final String DEFAULT_PASSWORD = "rocks";
+
+    private static final String PUBLIC_IP = "publicIp";
+    private static final String PORT = "port";
+    private static final String USERNAME = "username";
+    private static final String PASSWORD = "password";
+    private static final String DEVICE_ID = "deviceId";
+
+    @Override
+    public boolean isValid() {
+        return hasOnlyFields(PUBLIC_IP, PORT, USERNAME, PASSWORD, DEVICE_ID) &&
+                publicIp() != null &&
+                deviceId() != null;
+    }
+
+    public IpAddress publicIp() {
+        String ip = get(PUBLIC_IP, null);
+        try {
+            return IpAddress.valueOf(ip);
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
+    }
+
+    public Integer port() {
+        String port = get(PORT, String.valueOf(DEFAULT_PORT));
+        try {
+            return Integer.valueOf(port);
+        } catch (NumberFormatException e) {
+            return DEFAULT_PORT;
+        }
+    }
+
+    public String username() {
+        return get(USERNAME, String.valueOf(DEFAULT_USERNAME));
+    }
+
+    public String password() {
+        return get(PASSWORD, String.valueOf(DEFAULT_PASSWORD));
+    }
+
+    public DeviceId deviceId() {
+        String did = get(DEVICE_ID, null);
+        try {
+            return DeviceId.deviceId(did);
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
+    }
+}
diff --git a/local/ce-fabric/src/main/java/org/opencord/ce/local/fabric/CarrierEthernetFabricManager.java b/local/ce-fabric/src/main/java/org/opencord/ce/local/fabric/CarrierEthernetFabricManager.java
new file mode 100644
index 0000000..249031b
--- /dev/null
+++ b/local/ce-fabric/src/main/java/org/opencord/ce/local/fabric/CarrierEthernetFabricManager.java
@@ -0,0 +1,312 @@
+/*
+ * 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.opencord.ce.local.fabric;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
+import org.onlab.packet.IpAddress;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.config.ConfigFactory;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.opencord.ce.api.models.CarrierEthernetForwardingConstruct;
+import org.opencord.ce.api.models.CarrierEthernetNetworkInterface;
+import org.opencord.ce.api.models.CarrierEthernetUni;
+import org.opencord.ce.api.models.EvcConnId;
+import org.opencord.ce.api.services.MetroNetworkVirtualNodeService;
+import org.opencord.ce.local.bigswitch.BigSwitchService;
+import org.slf4j.Logger;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages a single switch CORD fabric for Carrier Ethernet services.
+ *
+ * Receives forwarding constructs from global orchestrator,
+ * and generates VLAN cross connect configuration for the
+ * ONOS fabric controller.
+ *
+ * No resources are allocated so only the node forwarding API is implemented.
+ */
+@Component(immediate = true)
+@Service
+public class CarrierEthernetFabricManager implements MetroNetworkVirtualNodeService {
+    private static final Logger log = getLogger(CarrierEthernetFabricManager.class);
+    private static final String APP_NAME = "org.opencord.ce.local.fabric";
+    private static final String APPS = "apps";
+    private static final String SEGMENT_ROUTING = "org.onosproject.segmentrouting";
+    private static final String XCONNECT = "xconnect";
+    private static final String VLAN = "vlan";
+    private static final String PORTS = "ports";
+    private static final String NAME = "name";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected BigSwitchService bigSwitchService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected NetworkConfigRegistry cfgService;
+
+    private final InternalConfigListener cfgListener = new InternalConfigListener();
+
+    private final ConfigFactory<ApplicationId, CarrierEthernetFabricConfig> configFactory =
+            new ConfigFactory<ApplicationId, CarrierEthernetFabricConfig>(APP_SUBJECT_FACTORY,
+                    CarrierEthernetFabricConfig.class, "segmentrouting_ctl", false) {
+                @Override
+                public CarrierEthernetFabricConfig createConfig() {
+                    return new CarrierEthernetFabricConfig();
+                }
+            };
+
+    private ApplicationId appId;
+    // TODO: use distributed maps via storage service
+    private Set<CarrierEthernetForwardingConstruct> forwardingConstructs = new LinkedHashSet<>();
+    private Map<EvcConnId, ConnectPoint> eePorts = new HashMap<>();
+    private Map<EvcConnId, ConnectPoint> upstreamPorts = new HashMap<>();
+    private IpAddress publicIp;
+    private Integer port;
+    private String username;
+    private String password;
+    private DeviceId deviceId;
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(APP_NAME);
+        cfgService.addListener(cfgListener);
+        cfgService.registerConfigFactory(configFactory);
+        cfgListener.doUpdate(cfgService.getConfig(appId, CarrierEthernetFabricConfig.class));
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        cfgService.removeListener(cfgListener);
+        cfgService.unregisterConfigFactory(configFactory);
+        log.info("Stopped");
+    }
+
+    @Override
+    public void setNodeForwarding(CarrierEthernetForwardingConstruct fc,
+                                  CarrierEthernetNetworkInterface srcNi,
+                                  Set<CarrierEthernetNetworkInterface> dstNiSet) {
+        addCrossconnect(fc, srcNi, dstNiSet);
+        postToSegmentRouting(buildConfig());
+    }
+
+    @Override
+    public void createBandwidthProfileResources(CarrierEthernetForwardingConstruct fc, CarrierEthernetUni uni) {
+        // No resources are allocated on the fabric
+        return;
+    }
+
+    @Override
+    public void applyBandwidthProfileResources(CarrierEthernetForwardingConstruct fc, CarrierEthernetUni uni) {
+        // No resources are allocated on the fabric
+        return;
+    }
+
+    @Override
+    public void removeBandwidthProfileResources(CarrierEthernetForwardingConstruct fc, CarrierEthernetUni uni) {
+        // No resources are allocated on the fabric
+        return;
+    }
+
+    @Override
+    public void removeAllForwardingResources(EvcConnId fcId) {
+        removeCrossconnect(fcId);
+        postToSegmentRouting(buildConfig());
+    }
+
+    /**
+     * Adds a fabric cross connect based on given Carrier Ethernet service.
+     *
+     * @param fc forwarding construct
+     * @param srcNi source network interface
+     * @param dstNiSet set of destination network interfaces
+     */
+    public void addCrossconnect(CarrierEthernetForwardingConstruct fc,
+                                CarrierEthernetNetworkInterface srcNi,
+                                Set<CarrierEthernetNetworkInterface> dstNiSet) {
+        // Store fc and extract physical fabric ports
+        Optional<ConnectPoint> eePort = bigSwitchService.connectPointFromVirtPort(srcNi.cp().port());
+        // Assume only a single upstream port is used, so we select randomly from set
+        CarrierEthernetNetworkInterface dstNi = dstNiSet.iterator().next();
+        Optional<ConnectPoint> upstreamPort = (dstNi == null) ? Optional.empty() :
+                bigSwitchService.connectPointFromVirtPort(dstNi.cp().port());
+        if (!eePort.isPresent() || !upstreamPort.isPresent()) {
+            log.error("Failed to install node forwarding, missing fabric ports: EE {} - upstream {}",
+                    eePort, upstreamPort);
+            return;
+        } else {
+            forwardingConstructs.add(fc);
+            eePorts.put(fc.id(), eePort.get());
+            upstreamPorts.put(fc.id(), upstreamPort.get());
+        }
+    }
+
+    /**
+     * Removes a fabric cross connect based on given forwarding construct.
+     *
+     * @param fcId forwarding construct id
+     */
+    public void removeCrossconnect(EvcConnId fcId) {
+        for (Iterator<CarrierEthernetForwardingConstruct> i = forwardingConstructs.iterator(); i.hasNext();) {
+            CarrierEthernetForwardingConstruct fc = i.next();
+            if (fcId.equals(fc.id())) {
+                forwardingConstructs.remove(fc);
+                break;
+            }
+        }
+        eePorts.remove(fcId);
+        upstreamPorts.remove(fcId);
+    }
+
+
+    /**
+     * All VLAN cross connects are rebuilt and pushed out since ONOS network config does not support updates.
+     *
+     * @return JSON with cross connect configuration for segment routing app
+     */
+    public JsonObject buildConfig() {
+        JsonArray xconnects = new JsonArray();
+        forwardingConstructs.stream()
+                .map(fc -> json(fc))
+                .forEach(fc -> xconnects.add(fc));
+
+        JsonObject dpid = new JsonObject();
+        dpid.add(deviceId.toString(), xconnects);
+
+        JsonObject xconnect = new JsonObject();
+        xconnect.add(XCONNECT, dpid);
+
+        JsonObject appName = new JsonObject();
+        appName.add(SEGMENT_ROUTING, xconnect);
+
+        JsonObject config = new JsonObject();
+        config.add(APPS, appName);
+
+        return config;
+    }
+
+    /**
+     * Execute REST POST to segment routing app with given VLAN cross connect config.
+     *
+     * @param json fabric VLAN cross connect configuration in json form
+     */
+    public void postToSegmentRouting(JsonObject json) {
+        // Setup credentials
+        HttpAuthenticationFeature feature = HttpAuthenticationFeature.basicBuilder()
+                .nonPreemptive()
+                .credentials(username, password)
+                .build();
+        ClientConfig cfg = new ClientConfig();
+        cfg.register(feature);
+        Client client = ClientBuilder.newClient(cfg);
+
+        // Build URL and perform post
+        WebTarget target = client.target("http://" + publicIp + ":" + port + "/onos/v1/network/configuration/");
+        Response response = target.request().post(Entity.entity(json, MediaType.APPLICATION_JSON_TYPE));
+        response.close();
+    }
+
+    /**
+     * Build fabric config json from forwarding construct.
+     *
+     * Example VLAN cross connect configuration for fabric
+     * "apps": {
+     *    "org.onosproject.segmentrouting": {
+     *       "xconnect": {
+     *          "of:0000000000000001": [{
+     *             "vlan": 10,
+     *             "ports": [5, 73],
+     *             "name": "OLT1"
+     *          }]
+     *       }
+     *    }
+     * }
+     */
+    private JsonObject json(CarrierEthernetForwardingConstruct fc) {
+        JsonObject jo = new JsonObject();
+        jo.addProperty(VLAN, fc.vlanId().toShort());
+
+        // First port is EE -> fabric, second is fabric -> upstream / CO egress
+        JsonArray ports = new JsonArray();
+        // FIXME: need to be more careful of nulls here
+        ports.add(eePorts.get(fc.id()).port().toLong());
+        ports.add(upstreamPorts.get(fc.id()).port().toLong());
+        jo.add(PORTS, ports);
+
+        jo.addProperty(NAME, fc.id().id());
+
+        return jo;
+    }
+
+    private class InternalConfigListener implements NetworkConfigListener {
+        private void doUpdate(CarrierEthernetFabricConfig cfg) {
+            if (cfg == null) {
+                log.error("Fabric config for VLAN xconnect missing");
+                return;
+            }
+
+            publicIp = cfg.publicIp();
+            port = cfg.port();
+            username = cfg.username();
+            password = cfg.password();
+            deviceId = cfg.deviceId();
+            log.info("Reconfigured");
+        }
+
+        @Override
+        public void event(NetworkConfigEvent event) {
+            if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
+                    event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) &&
+                    event.configClass().equals(CarrierEthernetFabricConfig.class)) {
+
+                if (event.config().isPresent()) {
+                    doUpdate((CarrierEthernetFabricConfig) event.config().get());
+                }
+            }
+        }
+    }
+}
diff --git a/local/ce-central-office/src/main/java/org/opencord/ce/local/co/package-info.java b/local/ce-fabric/src/main/java/org/opencord/ce/local/fabric/package-info.java
similarity index 85%
rename from local/ce-central-office/src/main/java/org/opencord/ce/local/co/package-info.java
rename to local/ce-fabric/src/main/java/org/opencord/ce/local/fabric/package-info.java
index c54876d..0b030a4 100644
--- a/local/ce-central-office/src/main/java/org/opencord/ce/local/co/package-info.java
+++ b/local/ce-fabric/src/main/java/org/opencord/ce/local/fabric/package-info.java
@@ -15,6 +15,6 @@
  */
 
 /**
- * Central Office CE app.
+ * Single switch CORD fabric support for Carrier Ethernet services.
  */
-package org.opencord.ce.local.co;
\ No newline at end of file
+package org.opencord.ce.local.fabric;
\ No newline at end of file
diff --git a/local/ce-fabric/src/test/java/org.opencord.ce.local.fabric/CarrierEthernetFabricManagerTest.java b/local/ce-fabric/src/test/java/org.opencord.ce.local.fabric/CarrierEthernetFabricManagerTest.java
new file mode 100644
index 0000000..cabcb77
--- /dev/null
+++ b/local/ce-fabric/src/test/java/org.opencord.ce.local.fabric/CarrierEthernetFabricManagerTest.java
@@ -0,0 +1,243 @@
+/*
+ * 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.opencord.ce.local.fabric;
+
+import com.google.common.collect.Sets;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.event.AbstractListenerManager;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.NetworkConfigRegistryAdapter;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.DeviceServiceAdapter;
+import org.onosproject.net.device.PortDescription;
+import org.onosproject.net.domain.DomainId;
+import org.opencord.ce.api.models.CarrierEthernetConnection;
+import org.opencord.ce.api.models.CarrierEthernetEnni;
+import org.opencord.ce.api.models.CarrierEthernetForwardingConstruct;
+import org.opencord.ce.api.models.CarrierEthernetInni;
+import org.opencord.ce.api.models.CarrierEthernetLogicalTerminationPoint;
+import org.opencord.ce.api.models.CarrierEthernetNetworkInterface;
+import org.opencord.ce.api.models.CarrierEthernetUni;
+import org.opencord.ce.api.models.EvcConnId;
+import org.opencord.ce.local.bigswitch.BigSwitchEvent;
+import org.opencord.ce.local.bigswitch.BigSwitchListener;
+import org.opencord.ce.local.bigswitch.BigSwitchService;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for {@link CarrierEthernetFabricManager}.
+ */
+public class CarrierEthernetFabricManagerTest {
+
+    private CarrierEthernetFabricManager target;
+    private static ServiceDirectory original;
+    private static TestServiceDirectory directory;
+
+    private CarrierEthernetLogicalTerminationPoint ltpSrc;
+    private CarrierEthernetLogicalTerminationPoint ltpDst;
+    // Physical connect points
+    private static ConnectPoint srcUniCp = ConnectPoint.deviceConnectPoint("netconf:10.0.0.10/1");
+    private ConnectPoint dstUniCp = ConnectPoint.deviceConnectPoint("netconf:10.0.0.20/1");
+    private ConnectPoint inniPhyCp = ConnectPoint.deviceConnectPoint("of:0000000000000001/1");
+    private ConnectPoint enni1PhyCp = ConnectPoint.deviceConnectPoint("of:0000000000000001/2");
+    private ConnectPoint enni2PhyCp = ConnectPoint.deviceConnectPoint("of:0000000000000001/3");
+    // Bigswitch (logical) connect points
+    private ConnectPoint inniBsCp = ConnectPoint.deviceConnectPoint("domain:0000000000000001/1");
+    private ConnectPoint enni1BsCp = ConnectPoint.deviceConnectPoint("domain:0000000000000001/2");
+    private ConnectPoint enni2BsCp = ConnectPoint.deviceConnectPoint("domain:0000000000000001/3");
+    // Carrier Ethernet models
+    private CarrierEthernetForwardingConstruct fc1;
+    private CarrierEthernetForwardingConstruct fc2;
+    private CarrierEthernetNetworkInterface srcUni;
+    private CarrierEthernetNetworkInterface dstUni;
+    private CarrierEthernetNetworkInterface inni;
+    private CarrierEthernetNetworkInterface enni1;
+    private CarrierEthernetNetworkInterface enni2;
+
+    private DeviceId deviceId = DeviceId.deviceId("of:0000000000000001");
+    private static VlanId vlan1 = VlanId.vlanId((short) 100);
+    private static VlanId vlan2 = VlanId.vlanId((short) 200);
+
+    @BeforeClass
+    public static void setUpBaseConfigClass() throws TestUtils.TestUtilsException {
+        directory = new TestServiceDirectory();
+        directory.add(DeviceService.class, new DeviceServiceAdapter(Collections.emptyList()));
+        original = TestUtils.getField(CarrierEthernetNetworkInterface.class, "serviceDirectory");
+        TestUtils.setField(CarrierEthernetNetworkInterface.class, "serviceDirectory", directory);
+    }
+
+    @AfterClass
+    public static void tearDownBaseConfigClass() throws TestUtils.TestUtilsException {
+        TestUtils.setField(CarrierEthernetNetworkInterface.class, "serviceDirectory", original);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        // Initialize the fabric manager
+        target = new CarrierEthernetFabricManager();
+        target.bigSwitchService = new TestBigSwitchService();
+        target.cfgService = new NetworkConfigRegistryAdapter();
+        target.coreService = new CoreServiceAdapter();
+
+        // Build UNIs
+        srcUni = CarrierEthernetUni.builder()
+                .cp(srcUniCp)
+                .build();
+        dstUni = CarrierEthernetUni.builder()
+                .cp(dstUniCp)
+                .build();
+        ltpSrc = new CarrierEthernetLogicalTerminationPoint("srcUni", srcUni);
+        ltpDst = new CarrierEthernetLogicalTerminationPoint("dstUni", dstUni);
+
+        // Build FC connecting UNIs
+        fc1 = CarrierEthernetForwardingConstruct.builder()
+                .cfgId("test1")
+                .id(EvcConnId.of("test1"))
+                .type(CarrierEthernetConnection.Type.POINT_TO_POINT)
+                .ltpSet(Sets.newHashSet(ltpSrc, ltpDst))
+                .build();
+        fc1.setVlanId(vlan1);
+        fc2 = CarrierEthernetForwardingConstruct.builder()
+                .cfgId("test2")
+                .id(EvcConnId.of("test2"))
+                .type(CarrierEthernetConnection.Type.POINT_TO_POINT)
+                .ltpSet(Sets.newHashSet(ltpSrc, ltpDst))
+                .build();
+        fc2.setVlanId(vlan2);
+        inni = CarrierEthernetInni.builder()
+                .cp(inniBsCp)
+                .build();
+        enni1 = CarrierEthernetEnni.builder()
+                .cp(enni1BsCp)
+                .build();
+        enni2 = CarrierEthernetEnni.builder()
+                .cp(enni2BsCp)
+                .build();
+
+        target.activate();
+    }
+
+    @After
+    public void tearDown() {
+        target.deactivate();
+    }
+
+    @Test
+    public void testBuildConfig() {
+        TestUtils.setField(target, "deviceId", deviceId);
+        JsonParser parser = new JsonParser();
+        JsonObject json;
+        String expected;
+
+        // Add first fc
+        target.addCrossconnect(fc1, inni, Sets.newHashSet(enni1));
+        json = target.buildConfig();
+        expected =
+                "{\"apps\":{" +
+                        "\"org.onosproject.segmentrouting\":{" +
+                        "\"xconnect\":{" +
+                        "\"of:0000000000000001\":[{" +
+                        "\"vlan\":" + vlan1.toString() + "," +
+                        "\"ports\":[1, 2]," +
+                        "\"name\": \"" + fc1.id().id() + "\"}" +
+                        "]}}}}";
+        assertEquals(json, parser.parse(expected).getAsJsonObject());
+
+        // Add second fc
+        target.addCrossconnect(fc2, inni, Sets.newHashSet(enni2));
+        json = target.buildConfig();
+        expected =
+                "{\"apps\":{" +
+                        "\"org.onosproject.segmentrouting\":{" +
+                            "\"xconnect\":{" +
+                                "\"of:0000000000000001\":[{" +
+                                    "\"vlan\":" + vlan1.toString() + "," +
+                                    "\"ports\":[1, 2]," +
+                                    "\"name\": \"" + fc1.id().id() + "\"}," +
+                                    "{\"vlan\":" + vlan2.toString() + "," +
+                                    "\"ports\":[1, 3]," +
+                                    "\"name\": \"" + fc2.id().id() + "\"}" +
+                                    "]}}}}";
+        assertEquals(json, parser.parse(expected).getAsJsonObject());
+
+        // Remove first fc
+        target.removeCrossconnect(fc1.id());
+        json = target.buildConfig();
+        expected =
+                "{\"apps\":{" +
+                        "\"org.onosproject.segmentrouting\":{" +
+                        "\"xconnect\":{" +
+                        "\"of:0000000000000001\":[{" +
+                        "\"vlan\":" + vlan2.toString() + "," +
+                        "\"ports\":[1, 3]," +
+                        "\"name\": \"" + fc2.id().id() + "\"}" +
+                        "]}}}}";
+        assertEquals(json, parser.parse(expected).getAsJsonObject());
+    }
+
+    private class TestBigSwitchService
+            extends AbstractListenerManager<BigSwitchEvent, BigSwitchListener>
+            implements BigSwitchService {
+
+        @Override
+        public List<PortDescription> getPorts() {
+            return null;
+        }
+
+        @Override
+        public PortNumber getPort(ConnectPoint port) {
+            return null;
+        }
+
+        @Override
+        public Optional<ConnectPoint> connectPointFromVirtPort(PortNumber portNumber) {
+            if (portNumber.toLong() == 1) {
+                return Optional.of(inniPhyCp);
+            }
+            if (portNumber.toLong() == 2) {
+                return Optional.of(enni1PhyCp);
+            }
+            if (portNumber.toLong() == 3) {
+                return Optional.of(enni2PhyCp);
+            }
+
+            return Optional.empty();
+        }
+
+        @Override
+        public DomainId domainId() {
+            return null;
+        }
+    }
+}
diff --git a/local/ce-vee/pom.xml b/local/ce-vee/pom.xml
index 7d7ec38..5ac2ee6 100644
--- a/local/ce-vee/pom.xml
+++ b/local/ce-vee/pom.xml
@@ -35,7 +35,10 @@
         <onos.app.name>org.opencord.ce.local.vee</onos.app.name>
         <onos.app.title>E-CORD Ethernet Edge app</onos.app.title>
         <onos.app.url>http://opencord.org</onos.app.url>
+        <onos.app.requires>org.opencord.ce.local.bigswitch</onos.app.requires>
+        <onos.app.requires>org.opencord.ce.local.channel.http</onos.app.requires>
     </properties>
+
     <dependencies>
         <dependency>
             <groupId>org.opencord.ce</groupId>
diff --git a/local/config-samples/co1-config.json b/local/config-samples/co1-onos-cord-cfg.json
similarity index 89%
rename from local/config-samples/co1-config.json
rename to local/config-samples/co1-onos-cord-cfg.json
index 222832d..a908151 100644
--- a/local/config-samples/co1-config.json
+++ b/local/config-samples/co1-onos-cord-cfg.json
@@ -8,7 +8,7 @@
           "connectPoint" : "netconf:10.0.0.10:830/1"
         },
         {
-          "mefPortType" : "UNI",
+          "mefPortType" : "INNI",
           "connectPoint" : "netconf:10.0.0.10:830/0",
           "interlinkId" : "cm-1"
         }
@@ -17,7 +17,7 @@
     "org.opencord.ce.local.channel.http" : {
       "global" : {
         "publicIp" : "10.128.14.1",
-        "port" : "8181",
+        "port" : "8182",
         "username" : "sdn",
         "password" : "rocks",
         "topic" : "ecord-domains-topic-one"
diff --git a/local/config-samples/co1-withEE-config.json b/local/config-samples/co1-withEE-onos-cord-cfg.json
similarity index 96%
rename from local/config-samples/co1-withEE-config.json
rename to local/config-samples/co1-withEE-onos-cord-cfg.json
index 6c1ef4c..e54cf63 100644
--- a/local/config-samples/co1-withEE-config.json
+++ b/local/config-samples/co1-withEE-onos-cord-cfg.json
@@ -29,7 +29,7 @@
     "org.opencord.ce.local.channel.http" : {
       "global" : {
         "publicIp" : "10.128.14.1",
-        "port" : "8181",
+        "port" : "8182",
         "username" : "sdn",
         "password" : "rocks",
         "topic" : "ecord-domains-topic-one"
diff --git a/local/config-samples/co2-config.json b/local/config-samples/co2-onos-cord-cfg.json
similarity index 89%
rename from local/config-samples/co2-config.json
rename to local/config-samples/co2-onos-cord-cfg.json
index 2b6f525..210c7c5 100644
--- a/local/config-samples/co2-config.json
+++ b/local/config-samples/co2-onos-cord-cfg.json
@@ -8,7 +8,7 @@
           "connectPoint" : "netconf:10.0.0.20:830/1"
         },
         {
-          "mefPortType" : "UNI",
+          "mefPortType" : "INNI",
           "connectPoint" : "netconf:10.0.0.20:830/0",
           "interlinkId" : "cm-1"
         }
@@ -17,7 +17,7 @@
     "org.opencord.ce.local.channel.http" : {
       "global" : {
         "publicIp" : "10.128.14.1",
-        "port" : "8181",
+        "port" : "8182",
         "username" : "sdn",
         "password" : "rocks",
         "topic" : "ecord-domains-topic-one"
diff --git a/local/config-samples/co2-withEE-config.json b/local/config-samples/co2-withEE-onos-cord-cfg.json
similarity index 96%
rename from local/config-samples/co2-withEE-config.json
rename to local/config-samples/co2-withEE-onos-cord-cfg.json
index 1140d7c..78b14b5 100644
--- a/local/config-samples/co2-withEE-config.json
+++ b/local/config-samples/co2-withEE-onos-cord-cfg.json
@@ -29,7 +29,7 @@
     "org.opencord.ce.local.channel.http" : {
       "global" : {
         "publicIp" : "10.128.14.1",
-        "port" : "8181",
+        "port" : "8182",
         "username" : "sdn",
         "password" : "rocks",
         "topic" : "ecord-domains-topic-one"
diff --git a/local/config-samples/onos-fabric-cfg.json b/local/config-samples/onos-fabric-cfg.json
new file mode 100644
index 0000000..a9a6f6f
--- /dev/null
+++ b/local/config-samples/onos-fabric-cfg.json
@@ -0,0 +1,38 @@
+{
+  "apps" : {
+    "org.opencord.ce.local.fabric" : {
+      "segmentrouting_ctl": {
+        "publicIp": "127.0.0.1",
+        "port": "8181",
+        "username": "sdn",
+        "password": "rocks",
+        "deviceId": "of:0000001e08095936"
+      }
+    },
+    "org.opencord.ce.local.bigswitch" : {
+      "mefPorts" :
+      [
+        {
+          "mefPortType" : "INNI",
+          "connectPoint" : "of:0000001e08095936/1",
+          "interlinkId" : "EE-2-fabric"
+        },
+        {
+          "mefPortType" : "ENNI",
+          "connectPoint" : "of:0000001e08095936/49",
+          "interlinkId" : "fabric-upstream"
+        }
+      ]
+    },
+    "org.opencord.ce.local.channel.http" : {
+      "global" : {
+        "publicIp" : "10.128.14.1",
+        "port" : "8182",
+        "username" : "sdn",
+        "password" : "rocks",
+        "topic" : "ecord-domains-topic-one"
+      }
+    }
+
+  }
+}
diff --git a/local/pom.xml b/local/pom.xml
index 199412e..ed76a1c 100644
--- a/local/pom.xml
+++ b/local/pom.xml
@@ -43,7 +43,7 @@
         <module>http-channel</module>
         <module>ce-transport</module>
         <module>ce-vee</module>
-        <module>ce-central-office</module>
+        <module>ce-fabric</module>
         <module>common-component</module>
     </modules>
 
@@ -53,6 +53,19 @@
             <artifactId>ce-api</artifactId>
             <version>${project.version}</version>
         </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-junit</artifactId>
+            <version>${onos.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>