Fixed REST API bug in Xconnect

In addition,
- Deprecated old XConnectHandler
- Fixed equals() of XconnectDesc and XconnectKey
- Implemented unit tests for XconnectCodec

Change-Id: I1b5f2c1f389523e3b65a3d9acaf75bc06e39fdda
diff --git a/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index c8c88c7..98ee40c 100644
--- a/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -96,7 +96,6 @@
 import org.onosproject.segmentrouting.config.DeviceConfiguration;
 import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
 import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
-import org.onosproject.segmentrouting.config.XConnectConfig;
 import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler;
 import org.onosproject.segmentrouting.grouphandler.DestinationSet;
 import org.onosproject.segmentrouting.grouphandler.NextNeighbors;
@@ -303,7 +302,6 @@
     private InternalLinkListener linkListener = null;
     private InternalDeviceListener deviceListener = null;
     private AppConfigHandler appCfgHandler = null;
-    public XConnectHandler xConnectHandler = null;
     McastHandler mcastHandler = null;
     HostHandler hostHandler = null;
     private RouteHandler routeHandler = null;
@@ -392,16 +390,6 @@
                 }
             };
 
-    private final ConfigFactory<ApplicationId, XConnectConfig> xConnectConfigFactory =
-            new ConfigFactory<ApplicationId, XConnectConfig>(
-                    SubjectFactories.APP_SUBJECT_FACTORY,
-                    XConnectConfig.class, "xconnect") {
-                @Override
-                public XConnectConfig createConfig() {
-                    return new XConnectConfig();
-                }
-            };
-
     private ConfigFactory<ApplicationId, McastConfig> mcastConfigFactory =
             new ConfigFactory<ApplicationId, McastConfig>(
                     SubjectFactories.APP_SUBJECT_FACTORY,
@@ -556,7 +544,6 @@
         linkListener = new InternalLinkListener();
         deviceListener = new InternalDeviceListener();
         appCfgHandler = new AppConfigHandler(this);
-        xConnectHandler = new XConnectHandler(this);
         mcastHandler = new McastHandler(this);
         hostHandler = new HostHandler(this);
         linkHandler = new LinkHandler(this);
@@ -568,7 +555,6 @@
         cfgService.addListener(cfgListener);
         cfgService.registerConfigFactory(deviceConfigFactory);
         cfgService.registerConfigFactory(appConfigFactory);
-        cfgService.registerConfigFactory(xConnectConfigFactory);
         cfgService.registerConfigFactory(mcastConfigFactory);
         log.info("Configuring network before adding listeners");
 
@@ -644,7 +630,6 @@
         cfgService.removeListener(cfgListener);
         cfgService.unregisterConfigFactory(deviceConfigFactory);
         cfgService.unregisterConfigFactory(appConfigFactory);
-        cfgService.unregisterConfigFactory(xConnectConfigFactory);
         cfgService.unregisterConfigFactory(mcastConfigFactory);
         compCfgService.unregisterProperties(getClass(), false);
 
@@ -1548,8 +1533,6 @@
                     } else if (configClass.equals(SegmentRoutingDeviceConfig.class)) {
                         log.info("Segment Routing Device Config added for {}", event.subject());
                         cfgListener.configureNetwork();
-                    } else if (configClass.equals(XConnectConfig.class)) {
-                        xConnectHandler.processXConnectConfigAdded(netcfgEvent);
                     } else if (configClass.equals(InterfaceConfig.class)) {
                         log.info("Interface Config added for {}", event.subject());
                         cfgListener.configureNetwork();
@@ -1566,8 +1549,6 @@
                     } else if (configClass.equals(SegmentRoutingDeviceConfig.class)) {
                         log.info("Segment Routing Device Config updated for {}", event.subject());
                         createOrUpdateDeviceConfiguration();
-                    } else if (configClass.equals(XConnectConfig.class)) {
-                        xConnectHandler.processXConnectConfigUpdated(netcfgEvent);
                     } else if (configClass.equals(InterfaceConfig.class)) {
                         log.info("Interface Config updated for {}", event.subject());
                         createOrUpdateDeviceConfiguration();
@@ -1586,8 +1567,6 @@
                     } else if (configClass.equals(SegmentRoutingDeviceConfig.class)) {
                         // TODO Handle sr device config removal
                         log.info("SegmentRoutingDeviceConfig removal is not handled in current implementation");
-                    } else if (configClass.equals(XConnectConfig.class)) {
-                        xConnectHandler.processXConnectConfigRemoved(netcfgEvent);
                     } else if (configClass.equals(InterfaceConfig.class)) {
                         // TODO Handle interface removal
                         log.info("InterfaceConfig removal is not handled in current implementation");
@@ -1658,7 +1637,6 @@
 
         if (mastershipService.isLocalMaster(deviceId)) {
             defaultRoutingHandler.populatePortAddressingRules(deviceId);
-            xConnectHandler.init(deviceId);
             DefaultGroupHandler groupHandler = groupHandlerMap.get(deviceId);
             groupHandler.createGroupsFromVlanConfig();
             routingRulePopulator.populateSubnetBroadcastRule(deviceId);
@@ -1697,7 +1675,6 @@
         defaultRoutingHandler
             .populateRoutingRulesForLinkStatusChange(null, null, device.id(), true);
         defaultRoutingHandler.purgeEcmpGraph(device.id());
-        xConnectHandler.removeDevice(device.id());
 
         // Cleanup all internal groupHandler stores for this device. Should be
         // done after all rerouting or rehashing has been completed
@@ -1897,7 +1874,6 @@
                 return;
             }
             checkState(appCfgHandler != null, "NetworkConfigEventHandler is not initialized");
-            checkState(xConnectHandler != null, "XConnectHandler is not initialized");
             switch (event.type()) {
                 case CONFIG_ADDED:
                 case CONFIG_UPDATED:
@@ -1924,8 +1900,7 @@
 
             if (!event.configClass().equals(SegmentRoutingDeviceConfig.class) &&
                     !event.configClass().equals(SegmentRoutingAppConfig.class) &&
-                    !event.configClass().equals(InterfaceConfig.class) &&
-                    !event.configClass().equals(XConnectConfig.class)) {
+                    !event.configClass().equals(InterfaceConfig.class)) {
                 log.debug("Ignore event {} due to class mismatch", event);
                 return false;
             }
diff --git a/app/src/main/java/org/onosproject/segmentrouting/XConnectHandler.java b/app/src/main/java/org/onosproject/segmentrouting/XConnectHandler.java
deleted file mode 100644
index 5dafb3f..0000000
--- a/app/src/main/java/org/onosproject/segmentrouting/XConnectHandler.java
+++ /dev/null
@@ -1,553 +0,0 @@
-/*
- * 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;
-
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-import org.onlab.packet.MacAddress;
-import org.onlab.packet.VlanId;
-import org.onlab.util.KryoNamespace;
-import org.onosproject.net.ConnectPoint;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.PortNumber;
-import org.onosproject.net.config.NetworkConfigEvent;
-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.flow.criteria.Criteria;
-import org.onosproject.net.flowobjective.DefaultFilteringObjective;
-import org.onosproject.net.flowobjective.DefaultForwardingObjective;
-import org.onosproject.net.flowobjective.DefaultNextObjective;
-import org.onosproject.net.flowobjective.DefaultObjectiveContext;
-import org.onosproject.net.flowobjective.FilteringObjective;
-import org.onosproject.net.flowobjective.ForwardingObjective;
-import org.onosproject.net.flowobjective.NextObjective;
-import org.onosproject.net.flowobjective.Objective;
-import org.onosproject.net.flowobjective.ObjectiveContext;
-import org.onosproject.net.flowobjective.ObjectiveError;
-import org.onosproject.segmentrouting.config.XConnectConfig;
-import org.onosproject.segmentrouting.storekey.XConnectStoreKey;
-import org.onosproject.store.serializers.KryoNamespaces;
-import org.onosproject.store.service.ConsistentMap;
-import org.onosproject.store.service.Serializer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-import java.util.stream.Collectors;
-
-/**
- * Handles cross connect related events.
- *
- * @deprecated in ONOS 1.12. Replaced by {@link org.onosproject.segmentrouting.xconnect.impl.XconnectManager}
- */
-@Deprecated
-public class XConnectHandler {
-    private static final Logger log = LoggerFactory.getLogger(XConnectHandler.class);
-    private static final String CONFIG_NOT_FOUND = "XConnect config not found";
-    private static final String NOT_MASTER = "Not master controller";
-    private final SegmentRoutingManager srManager;
-    private final ConsistentMap<XConnectStoreKey, NextObjective> xConnectNextObjStore;
-
-    XConnectHandler(SegmentRoutingManager srManager) {
-        this.srManager = srManager;
-        KryoNamespace.Builder xConnectKryo = new KryoNamespace.Builder()
-                .register(KryoNamespaces.API)
-                .register(XConnectStoreKey.class)
-                .register(NextObjContext.class);
-        xConnectNextObjStore = srManager.storageService
-                .<XConnectStoreKey, NextObjective>consistentMapBuilder()
-                .withName("onos-xconnect-nextobj-store")
-                .withSerializer(Serializer.using(xConnectKryo.build()))
-                .build();
-    }
-
-    /**
-     * Read initial XConnect for given device.
-     *
-     * @param deviceId ID of the device to be initialized
-     */
-    public void init(DeviceId deviceId) {
-        // Try to read XConnect config
-        XConnectConfig config =
-                srManager.cfgService.getConfig(srManager.appId, XConnectConfig.class);
-        if (config == null) {
-            log.info("Skip XConnect initialization: {}", CONFIG_NOT_FOUND);
-            return;
-        }
-
-        config.getXconnects(deviceId).forEach(key -> populateXConnect(key, config.getPorts(key)));
-    }
-
-    /**
-     * Processes Segment Routing App Config added event.
-     *
-     * @param event network config added event
-     */
-    void processXConnectConfigAdded(NetworkConfigEvent event) {
-        log.info("Processing XConnect CONFIG_ADDED");
-        XConnectConfig config = (XConnectConfig) event.config().get();
-        config.getXconnects().forEach(key -> populateXConnect(key, config.getPorts(key)));
-    }
-
-    /**
-     * Processes Segment Routing App Config updated event.
-     *
-     * @param event network config updated event
-     */
-    void processXConnectConfigUpdated(NetworkConfigEvent event) {
-        log.info("Processing XConnect CONFIG_UPDATED");
-        XConnectConfig prevConfig = (XConnectConfig) event.prevConfig().get();
-        XConnectConfig config = (XConnectConfig) event.config().get();
-        Set<XConnectStoreKey> prevKeys = prevConfig.getXconnects();
-        Set<XConnectStoreKey> keys = config.getXconnects();
-
-        Set<XConnectStoreKey> pendingRemove = prevKeys.stream()
-                .filter(key -> !keys.contains(key)).collect(Collectors.toSet());
-        Set<XConnectStoreKey> pendingAdd = keys.stream()
-                .filter(key -> !prevKeys.contains(key)).collect(Collectors.toSet());
-        Set<XConnectStoreKey> pendingUpdate = keys.stream()
-                .filter(key -> prevKeys.contains(key) &&
-                        !config.getPorts(key).equals(prevConfig.getPorts(key)))
-                .collect(Collectors.toSet());
-
-        pendingRemove.forEach(key -> revokeXConnect(key, prevConfig.getPorts(key)));
-        pendingAdd.forEach(key -> populateXConnect(key, config.getPorts(key)));
-        pendingUpdate.forEach(key ->
-                updateXConnect(key, prevConfig.getPorts(key), config.getPorts(key)));
-    }
-
-    /**
-     * Processes Segment Routing App Config removed event.
-     *
-     * @param event network config removed event
-     */
-    void processXConnectConfigRemoved(NetworkConfigEvent event) {
-        log.info("Processing XConnect CONFIG_REMOVED");
-        XConnectConfig prevConfig = (XConnectConfig) event.prevConfig().get();
-        prevConfig.getXconnects().forEach(key -> {
-            revokeXConnect(key, prevConfig.getPorts(key));
-        });
-    }
-
-    /**
-     * Checks if there is any XConnect configured on given connect point.
-     *
-     * @param cp connect point
-     * @return true if there is XConnect configured on given connect point.
-     */
-    public boolean hasXConnect(ConnectPoint cp) {
-        // Try to read XConnect config
-        XConnectConfig config =
-                srManager.cfgService.getConfig(srManager.appId, XConnectConfig.class);
-        if (config == null) {
-            log.warn("Failed to read XConnect config: {}", CONFIG_NOT_FOUND);
-            return false;
-        }
-        return config.getXconnects(cp.deviceId()).stream()
-                .anyMatch(key -> config.getPorts(key).contains(cp.port()));
-    }
-
-    /**
-     * 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 (!srManager.mastershipService.isLocalMaster(key.deviceId())) {
-            log.info("Abort populating XConnect {}: {}", key, NOT_MASTER);
-            return;
-        }
-
-        ports = addPairPort(key.deviceId(), ports);
-        populateFilter(key, ports);
-        populateFwd(key, populateNext(key, ports));
-        populateAcl(key);
-    }
-
-    /**
-     * Populates filtering objectives for given XConnect.
-     *
-     * @param key XConnect store key
-     * @param ports XConnect 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));
-            srManager.flowObjectiveService.filter(key.deviceId(), filtObjBuilder.add(context));
-        });
-    }
-
-    /**
-     * Populates next objectives for given XConnect.
-     *
-     * @param key XConnect store key
-     * @param ports XConnect ports
-     */
-    private NextObjective populateNext(XConnectStoreKey key, Set<PortNumber> ports) {
-        NextObjective nextObj;
-        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);
-            srManager.flowObjectiveService.next(key.deviceId(), nextObj);
-            xConnectNextObjStore.put(key, nextObj);
-            log.debug("NextObj for {} not found. Creating new NextObj with id={}", key, nextObj.id());
-        }
-        return nextObj;
-    }
-
-    /**
-     * Populates bridging forwarding objectives for given XConnect.
-     *
-     * @param key XConnect store key
-     * @param nextObj next objective
-     */
-    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));
-        srManager.flowObjectiveService.forward(key.deviceId(), fwdObjBuilder.add(fwdContext));
-    }
-
-    /**
-     * Populates ACL forwarding objectives for given XConnect.
-     *
-     * @param key XConnect store key
-     */
-    private void populateAcl(XConnectStoreKey key) {
-        ForwardingObjective.Builder aclObjBuilder = aclObjBuilder(key.vlanId());
-        ObjectiveContext aclContext = new DefaultObjectiveContext(
-                (objective) -> log.debug("XConnect AclObj for {} populated", key),
-                (objective, error) ->
-                        log.warn("Failed to populate XConnect AclObj for {}: {}", key, error));
-        srManager.flowObjectiveService.forward(key.deviceId(), aclObjBuilder.add(aclContext));
-    }
-
-    /**
-     * Revokes XConnect groups and flows for given key.
-     *
-     * @param key XConnect key
-     * @param ports XConnect ports
-     */
-    private void revokeXConnect(XConnectStoreKey key, Set<PortNumber> ports) {
-        if (!srManager.mastershipService.isLocalMaster(key.deviceId())) {
-            log.info("Abort populating XConnect {}: {}", key, NOT_MASTER);
-            return;
-        }
-
-        ports = addPairPort(key.deviceId(), ports);
-        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);
-        }
-        revokeAcl(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));
-            srManager.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);
-                }
-            }
-        };
-        srManager.flowObjectiveService.next(key.deviceId(),
-                (NextObjective) nextObj.copy().remove(context));
-        xConnectNextObjStore.remove(key);
-    }
-
-    /**
-     * Revokes bridging 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);
-                }
-            }
-        };
-        srManager.flowObjectiveService
-                .forward(key.deviceId(), fwdObjBuilder.remove(context));
-    }
-
-    /**
-     * Revokes ACL forwarding objectives for given XConnect.
-     *
-     * @param key XConnect store key
-     */
-    private void revokeAcl(XConnectStoreKey key) {
-        ForwardingObjective.Builder aclObjBuilder = aclObjBuilder(key.vlanId());
-        ObjectiveContext aclContext = new DefaultObjectiveContext(
-                (objective) -> log.debug("XConnect AclObj for {} populated", key),
-                (objective, error) ->
-                        log.warn("Failed to populate XConnect AclObj for {}: {}", key, error));
-        srManager.flowObjectiveService
-                .forward(key.deviceId(), aclObjBuilder.remove(aclContext));
-    }
-
-    /**
-     * Updates XConnect groups and flows for given key.
-     *
-     * @param key XConnect key
-     * @param prevPorts previous XConnect ports
-     * @param ports new XConnect ports
-     */
-    private void updateXConnect(XConnectStoreKey key, Set<PortNumber> prevPorts,
-            Set<PortNumber> ports) {
-        // NOTE: ACL flow doesn't include port information. No need to update it.
-        //       Pair port is built-in and thus not going to change. No need to update it.
-
-        // remove old filter
-        prevPorts.stream().filter(port -> !ports.contains(port)).forEach(port ->
-            revokeFilter(key, ImmutableSet.of(port)));
-        // install new filter
-        ports.stream().filter(port -> !prevPorts.contains(port)).forEach(port ->
-            populateFilter(key, ImmutableSet.of(port)));
-
-        CompletableFuture<ObjectiveError> fwdFuture = new CompletableFuture<>();
-        CompletableFuture<ObjectiveError> nextFuture = new CompletableFuture<>();
-
-        if (xConnectNextObjStore.containsKey(key)) {
-            NextObjective nextObj = xConnectNextObjStore.get(key).value();
-            revokeFwd(key, nextObj, fwdFuture);
-
-            fwdFuture.thenAcceptAsync(fwdStatus -> {
-                if (fwdStatus == null) {
-                    log.debug("Fwd removed. Now remove group {}", key);
-                    revokeNext(key, nextObj, nextFuture);
-                }
-            });
-
-            nextFuture.thenAcceptAsync(nextStatus -> {
-                if (nextStatus == null) {
-                    log.debug("Installing new group and flow for {}", key);
-                    populateFwd(key, populateNext(key, ports));
-                }
-            });
-        } else {
-            log.warn("NextObj for {} does not exist in the store.", key);
-        }
-    }
-
-    /**
-     * Remove all groups on given device.
-     *
-     * @param deviceId device ID
-     */
-    void removeDevice(DeviceId deviceId) {
-        xConnectNextObjStore.entrySet().stream()
-                .filter(entry -> entry.getKey().deviceId().equals(deviceId))
-                .forEach(entry -> xConnectNextObjStore.remove(entry.getKey()));
-        log.debug("{} is removed from xConnectNextObjStore", deviceId);
-    }
-
-    /**
-     * Creates a next objective builder for XConnect.
-     *
-     * @param key XConnect key
-     * @param ports set of XConnect ports
-     * @return next objective builder
-     */
-    private NextObjective.Builder nextObjBuilder(XConnectStoreKey key, Set<PortNumber> ports) {
-        int nextId = srManager.flowObjectiveService.allocateNextId();
-        TrafficSelector metadata =
-                DefaultTrafficSelector.builder().matchVlanId(key.vlanId()).build();
-        NextObjective.Builder nextObjBuilder = DefaultNextObjective
-                .builder().withId(nextId)
-                .withType(NextObjective.Type.BROADCAST).fromApp(srManager.appId)
-                .withMeta(metadata);
-        ports.forEach(port -> {
-            TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
-            tBuilder.setOutput(port);
-            nextObjBuilder.addTreatment(tBuilder.build());
-        });
-        return nextObjBuilder;
-    }
-
-    /**
-     * Creates a bridging forwarding objective builder for XConnect.
-     *
-     * @param key XConnect key
-     * @param nextId next ID of the broadcast group for this XConnect key
-     * @return forwarding objective builder
-     */
-    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(SegmentRoutingService.XCONNECT_PRIORITY)
-                .fromApp(srManager.appId)
-                .makePermanent();
-        return fob;
-    }
-
-    /**
-     * Creates an ACL forwarding objective builder for XConnect.
-     *
-     * @param vlanId cross connect VLAN id
-     * @return forwarding objective builder
-     */
-    private ForwardingObjective.Builder aclObjBuilder(VlanId vlanId) {
-        TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
-        sbuilder.matchVlanId(vlanId);
-
-        TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
-
-        ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
-        fob.withFlag(ForwardingObjective.Flag.VERSATILE)
-                .withSelector(sbuilder.build())
-                .withTreatment(tbuilder.build())
-                .withPriority(SegmentRoutingService.XCONNECT_ACL_PRIORITY)
-                .fromApp(srManager.appId)
-                .makePermanent();
-        return fob;
-    }
-
-    /**
-     * Creates a filtering objective builder for XConnect.
-     *
-     * @param key XConnect key
-     * @param port XConnect ports
-     * @return next objective builder
-     */
-    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(SegmentRoutingService.XCONNECT_PRIORITY);
-        return fob.permit().fromApp(srManager.appId);
-    }
-
-    /**
-     * Add pair port to the given set of port.
-     *
-     * @param deviceId device Id
-     * @param ports ports specified in the xconnect config
-     * @return port specified in the xconnect config plus the pair port (if configured)
-     */
-    private Set<PortNumber> addPairPort(DeviceId deviceId, Set<PortNumber> ports) {
-        Set<PortNumber> newPorts = Sets.newHashSet(ports);
-        srManager.getPairLocalPort(deviceId).ifPresent(newPorts::add);
-        return newPorts;
-    }
-
-    // TODO: Lambda closure in DefaultObjectiveContext cannot be serialized properly
-    //       with Kryo 3.0.3. It will be fixed in 3.0.4. By then we can use
-    //       DefaultObjectiveContext again.
-    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);
-        }
-    }
-}
diff --git a/app/src/main/java/org/onosproject/segmentrouting/config/XConnectConfig.java b/app/src/main/java/org/onosproject/segmentrouting/config/XConnectConfig.java
deleted file mode 100644
index 6689b93..0000000
--- a/app/src/main/java/org/onosproject/segmentrouting/config/XConnectConfig.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.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 org.onosproject.segmentrouting.storekey.XConnectStoreKey;
-
-import java.util.Set;
-
-import static com.google.common.base.Preconditions.checkArgument;
-
-/**
- * Configuration object for cross-connect.
- *
- * @deprecated in ONOS 1.12. Replaced by {@link org.onosproject.segmentrouting.xconnect.impl.XconnectManager}
- */
-@Deprecated
-public class XConnectConfig extends Config<ApplicationId> {
-    private static final String VLAN = "vlan";
-    private static final String PORTS = "ports";
-    private static final String NAME = "name"; // dummy field for naming
-
-    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, NAME)) {
-                    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/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectCodec.java b/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectCodec.java
index 1154e40..83421bc 100644
--- a/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectCodec.java
+++ b/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectCodec.java
@@ -32,9 +32,9 @@
  * Codec for Xconnect.
  */
 public class XconnectCodec extends JsonCodec<XconnectDesc> {
-    private static final String DEVICE_ID = "deviceId";
-    private static final String VLAN_ID = "vlanId";
-    private static final String ENDPOINTS = "endpoints";
+    static final String DEVICE_ID = "deviceId";
+    static final String VLAN_ID = "vlanId";
+    static final String ENDPOINTS = "endpoints";
 
     private static Logger log = LoggerFactory.getLogger(XconnectCodec.class);
 
@@ -42,7 +42,7 @@
     public ObjectNode encode(XconnectDesc desc, CodecContext context) {
         final ObjectNode result = context.mapper().createObjectNode();
         result.put(DEVICE_ID, desc.key().deviceId().toString());
-        result.put(VLAN_ID, desc.key().vlanId().toString());
+        result.put(VLAN_ID, desc.key().vlanId().toShort());
         final ArrayNode portNode = result.putArray(ENDPOINTS);
         desc.endpoints().forEach(endpoint -> portNode.add(endpoint.toString()));
 
@@ -57,8 +57,7 @@
         Set<XconnectEndpoint> endpoints = Sets.newHashSet();
         JsonNode endpointNodes = json.get(ENDPOINTS);
         if (endpointNodes != null) {
-            XconnectEndpoint endpoint = XconnectEndpoint.fromString(endpointNodes.asText());
-            endpointNodes.forEach(endpointNode -> endpoints.add(endpoint));
+            endpointNodes.forEach(endpointNode -> endpoints.add(XconnectEndpoint.fromString(endpointNode.asText())));
         }
 
         XconnectKey key = new XconnectKey(deviceId, vlanId);
diff --git a/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectDesc.java b/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectDesc.java
index 1e7a3b2..a47823a 100644
--- a/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectDesc.java
+++ b/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectDesc.java
@@ -61,9 +61,6 @@
         if (this == obj) {
             return true;
         }
-        if (!super.equals(obj)) {
-            return false;
-        }
         if (!(obj instanceof XconnectDesc)) {
             return false;
         }
diff --git a/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectKey.java b/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectKey.java
index 1f2d446..7d433cd 100644
--- a/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectKey.java
+++ b/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectKey.java
@@ -62,9 +62,6 @@
         if (this == obj) {
             return true;
         }
-        if (!super.equals(obj)) {
-            return false;
-        }
         if (!(obj instanceof XconnectKey)) {
             return false;
         }
diff --git a/app/src/test/java/org/onosproject/segmentrouting/config/XConnectConfigTest.java b/app/src/test/java/org/onosproject/segmentrouting/config/XConnectConfigTest.java
deleted file mode 100644
index 62d2d86..0000000
--- a/app/src/test/java/org/onosproject/segmentrouting/config/XConnectConfigTest.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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.VlanId;
-import org.onosproject.TestApplicationId;
-import org.onosproject.core.ApplicationId;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.PortNumber;
-import org.onosproject.net.config.Config;
-import org.onosproject.net.config.ConfigApplyDelegate;
-import org.onosproject.segmentrouting.SegmentRoutingManager;
-import org.onosproject.segmentrouting.storekey.XConnectStoreKey;
-import java.io.InputStream;
-import java.util.Set;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.assertThat;
-import static org.hamcrest.Matchers.is;
-
-/**
- * Tests for class {@link XConnectConfig}.
- */
-public class XConnectConfigTest {
-    private static final DeviceId DEV1 = DeviceId.deviceId("of:0000000000000001");
-    private static final DeviceId DEV2 = DeviceId.deviceId("of:0000000000000002");
-    private static final VlanId VLAN10 = VlanId.vlanId((short) 10);
-    private static final VlanId VLAN20 = VlanId.vlanId((short) 20);
-    private static final PortNumber PORT3 = PortNumber.portNumber(3);
-    private static final PortNumber PORT4 = PortNumber.portNumber(4);
-    private static final PortNumber PORT5 = PortNumber.portNumber(5);
-    private static final XConnectStoreKey KEY1 = new XConnectStoreKey(DEV1, VLAN10);
-    private static final XConnectStoreKey KEY2 = new XConnectStoreKey(DEV2, VLAN10);
-    private static final XConnectStoreKey KEY3 = new XConnectStoreKey(DEV2, VLAN20);
-    private static final XConnectStoreKey KEY4 = new XConnectStoreKey(DEV2, VlanId.NONE);
-
-    private XConnectConfig config;
-    private XConnectConfig invalidConfig;
-
-    @Before
-    public void setUp() throws Exception {
-        InputStream jsonStream = SegmentRoutingAppConfigTest.class
-                .getResourceAsStream("/xconnect.json");
-        InputStream invalidJsonStream = SegmentRoutingAppConfigTest.class
-                .getResourceAsStream("/xconnect-invalid.json");
-
-        String key = SegmentRoutingManager.APP_NAME;
-        ApplicationId subject = new TestApplicationId(key);
-        ObjectMapper mapper = new ObjectMapper();
-        JsonNode jsonNode = mapper.readTree(jsonStream);
-        JsonNode invalidJsonNode = mapper.readTree(invalidJsonStream);
-        ConfigApplyDelegate delegate = new XConnectConfigTest.MockDelegate();
-
-        config = new XConnectConfig();
-        config.init(subject, key, jsonNode, mapper, delegate);
-        invalidConfig = new XConnectConfig();
-        invalidConfig.init(subject, key, invalidJsonNode, mapper, delegate);
-    }
-
-    /**
-     * Tests config validity.
-     */
-    @Test
-    public void testIsValid() {
-        assertTrue(config.isValid());
-        assertFalse(invalidConfig.isValid());
-    }
-
-    /**
-     * Tests getXconnects.
-     */
-    @Test
-    public void testGetXconnects() {
-        Set<XConnectStoreKey> xconnects = config.getXconnects();
-        assertThat(xconnects.size(), is(3));
-        assertTrue(xconnects.contains(KEY1));
-        assertTrue(xconnects.contains(KEY2));
-        assertTrue(xconnects.contains(KEY3));
-        assertFalse(xconnects.contains(KEY4));
-    }
-
-    /**
-     * Tests getPorts.
-     */
-    @Test
-    public void testGetPorts() {
-        Set<PortNumber> ports;
-
-        ports = config.getPorts(KEY1);
-        assertThat(ports.size(), is(2));
-        assertTrue(ports.contains(PORT3));
-        assertTrue(ports.contains(PORT4));
-
-        ports = config.getPorts(KEY2);
-        assertThat(ports.size(), is(2));
-        assertTrue(ports.contains(PORT3));
-        assertTrue(ports.contains(PORT4));
-
-        ports = config.getPorts(KEY3);
-        assertThat(ports.size(), is(2));
-        assertTrue(ports.contains(PORT4));
-        assertTrue(ports.contains(PORT5));
-    }
-
-    private class MockDelegate implements ConfigApplyDelegate {
-        @Override
-        public void onApply(Config config) {
-        }
-    }
-}
\ No newline at end of file
diff --git a/app/src/test/java/org/onosproject/segmentrouting/xconnect/api/XconnectCodecTest.java b/app/src/test/java/org/onosproject/segmentrouting/xconnect/api/XconnectCodecTest.java
new file mode 100644
index 0000000..bac9ff4
--- /dev/null
+++ b/app/src/test/java/org/onosproject/segmentrouting/xconnect/api/XconnectCodecTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2019-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.xconnect.api;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.Sets;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.VlanId;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.codec.impl.MockCodecContext;
+import org.onosproject.net.DeviceId;
+
+import java.io.InputStream;
+import java.util.Set;
+
+import static org.junit.Assert.*;
+
+public class XconnectCodecTest {
+    private static final DeviceId DEVICE_ID = DeviceId.deviceId("of:1");
+    private static final VlanId VLAN_VID = VlanId.vlanId((short) 10);
+    private static final XconnectKey KEY = new XconnectKey(DEVICE_ID, VLAN_VID);
+    private static final XconnectEndpoint EP1 = XconnectEndpoint.fromString("1");
+    private static final XconnectEndpoint EP2 = XconnectEndpoint.fromString("2");
+    private static final XconnectEndpoint EP3 = XconnectEndpoint.fromString("LB:5");
+
+    private CodecContext context;
+    private JsonCodec<XconnectDesc> codec;
+
+    @Before
+    public void setUp() throws Exception {
+        context = new MockCodecContext();
+        codec = new XconnectCodec();
+    }
+
+    @Test
+    public void testEncodePort() throws Exception {
+        Set<XconnectEndpoint> endpoints1 = Sets.newHashSet(EP1, EP2);
+        XconnectDesc desc1 = new XconnectDesc(KEY, endpoints1);
+
+        ObjectMapper mapper = new ObjectMapper();
+        InputStream jsonStream1 = XconnectCodecTest.class.getResourceAsStream("/xconnect1.json");
+        JsonNode expected = mapper.readTree(jsonStream1);
+
+        JsonNode actual = codec.encode(desc1, context);
+
+        assertEquals(expected.get(XconnectCodec.DEVICE_ID), actual.get(XconnectCodec.DEVICE_ID));
+        assertEquals(expected.get(XconnectCodec.VLAN_ID).asInt(), actual.get(XconnectCodec.VLAN_ID).asInt());
+        assertEquals(expected.get(XconnectCodec.ENDPOINTS), actual.get(XconnectCodec.ENDPOINTS));
+    }
+
+    @Test
+    public void testDecodePort() throws Exception {
+        Set<XconnectEndpoint> endpoints1 = Sets.newHashSet(EP1, EP2);
+        XconnectDesc expected = new XconnectDesc(KEY, endpoints1);
+
+        ObjectMapper mapper = new ObjectMapper();
+        InputStream jsonStream1 = XconnectCodecTest.class.getResourceAsStream("/xconnect1.json");
+        ObjectNode objectNode = mapper.readTree(jsonStream1).deepCopy();
+
+        XconnectDesc actual = codec.decode(objectNode, context);
+
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    public void testEncodeLb() throws Exception {
+        Set<XconnectEndpoint> endpoints1 = Sets.newHashSet(EP1, EP3);
+        XconnectDesc desc1 = new XconnectDesc(KEY, endpoints1);
+
+        ObjectMapper mapper = new ObjectMapper();
+        InputStream jsonStream1 = XconnectCodecTest.class.getResourceAsStream("/xconnect2.json");
+        JsonNode expected = mapper.readTree(jsonStream1);
+
+        JsonNode actual = codec.encode(desc1, context);
+
+        assertEquals(expected.get(XconnectCodec.DEVICE_ID), actual.get(XconnectCodec.DEVICE_ID));
+        assertEquals(expected.get(XconnectCodec.VLAN_ID).asInt(), actual.get(XconnectCodec.VLAN_ID).asInt());
+        assertEquals(expected.get(XconnectCodec.ENDPOINTS), actual.get(XconnectCodec.ENDPOINTS));
+    }
+
+    @Test
+    public void testDecodeLb() throws Exception {
+        Set<XconnectEndpoint> endpoints1 = Sets.newHashSet(EP1, EP3);
+        XconnectDesc expected = new XconnectDesc(KEY, endpoints1);
+
+        ObjectMapper mapper = new ObjectMapper();
+        InputStream jsonStream1 = XconnectCodecTest.class.getResourceAsStream("/xconnect2.json");
+        ObjectNode objectNode = mapper.readTree(jsonStream1).deepCopy();
+
+        XconnectDesc actual = codec.decode(objectNode, context);
+
+        assertEquals(expected, actual);
+    }
+}
\ No newline at end of file
diff --git a/app/src/test/resources/xconnect-invalid.json b/app/src/test/resources/xconnect-invalid.json
deleted file mode 100644
index e468271..0000000
--- a/app/src/test/resources/xconnect-invalid.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-  "of:0000000000000001": [
-    {
-      "vlan": 10,
-      "ports": [3, 4]
-    }
-  ],
-  "of:0000000000000002": [
-    {
-      "vlan": 10,
-      "ports": [3, 4]
-    },
-    {
-      "vlan": 20,
-      "ports": [4, 5, 6]
-    }
-  ]
-}
diff --git a/app/src/test/resources/xconnect.json b/app/src/test/resources/xconnect.json
deleted file mode 100644
index ebd61b3..0000000
--- a/app/src/test/resources/xconnect.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-  "of:0000000000000001": [
-    {
-      "vlan": 10,
-      "ports": [3, 4],
-      "name": "OLT1"
-    }
-  ],
-  "of:0000000000000002": [
-    {
-      "vlan": 10,
-      "ports": [3, 4]
-    },
-    {
-      "vlan": 20,
-      "ports": [4, 5]
-    }
-  ]
-}
diff --git a/app/src/test/resources/xconnect1.json b/app/src/test/resources/xconnect1.json
new file mode 100644
index 0000000..d8990d4
--- /dev/null
+++ b/app/src/test/resources/xconnect1.json
@@ -0,0 +1,5 @@
+{
+  "deviceId": "of:1",
+  "vlanId": 10,
+  "endpoints": ["1", "2"]
+}
diff --git a/app/src/test/resources/xconnect2.json b/app/src/test/resources/xconnect2.json
new file mode 100644
index 0000000..61919da
--- /dev/null
+++ b/app/src/test/resources/xconnect2.json
@@ -0,0 +1,5 @@
+{
+  "deviceId": "of:1",
+  "vlanId": 10,
+  "endpoints": ["1", "LB:5"]
+}