Address comments in gerrit 20328

Create a new class XconnecEndpoint to cover both physical port and load balancer
Also change the CLI load balancer identifier to "LB:"

Change-Id: I0b4cd6e474d8b21468d87fcadd9280fdf7d6aafa
diff --git a/app/src/main/java/org/onosproject/segmentrouting/xconnect/impl/XconnectManager.java b/app/src/main/java/org/onosproject/segmentrouting/xconnect/impl/XconnectManager.java
index d8f03aa..80c1c80 100644
--- a/app/src/main/java/org/onosproject/segmentrouting/xconnect/impl/XconnectManager.java
+++ b/app/src/main/java/org/onosproject/segmentrouting/xconnect/impl/XconnectManager.java
@@ -74,7 +74,10 @@
 import org.onosproject.segmentrouting.storekey.VlanNextObjectiveStoreKey;
 import org.onosproject.segmentrouting.xconnect.api.XconnectCodec;
 import org.onosproject.segmentrouting.xconnect.api.XconnectDesc;
+import org.onosproject.segmentrouting.xconnect.api.XconnectEndpoint;
 import org.onosproject.segmentrouting.xconnect.api.XconnectKey;
+import org.onosproject.segmentrouting.xconnect.api.XconnectLoadBalancerEndpoint;
+import org.onosproject.segmentrouting.xconnect.api.XconnectPortEndpoint;
 import org.onosproject.segmentrouting.xconnect.api.XconnectService;
 import org.onosproject.store.serializers.KryoNamespaces;
 import org.onosproject.store.service.ConsistentMap;
@@ -147,7 +150,7 @@
     HostService hostService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
-    L2LbService l2LbService;
+    private L2LbService l2LbService;
 
     private static final String APP_NAME = "org.onosproject.xconnect";
     private static final String ERROR_NOT_LEADER = "Not leader controller";
@@ -157,13 +160,13 @@
     private static Logger log = LoggerFactory.getLogger(XconnectManager.class);
 
     private ApplicationId appId;
-    private ConsistentMap<XconnectKey, Set<String>> xconnectStore;
+    private ConsistentMap<XconnectKey, Set<XconnectEndpoint>> xconnectStore;
     private ConsistentMap<XconnectKey, Integer> xconnectNextObjStore;
 
     private ConsistentMap<VlanNextObjectiveStoreKey, Integer> xconnectMulticastNextStore;
     private ConsistentMap<VlanNextObjectiveStoreKey, List<PortNumber>> xconnectMulticastPortsStore;
 
-    private final MapEventListener<XconnectKey, Set<String>> xconnectListener = new XconnectMapListener();
+    private final MapEventListener<XconnectKey, Set<XconnectEndpoint>> xconnectListener = new XconnectMapListener();
     private ExecutorService xConnectExecutor;
 
     private final DeviceListener deviceListener = new InternalDeviceListener();
@@ -178,8 +181,6 @@
     private Cache<L2LbId, XconnectKey> l2LbCache;
     // Executor for the cache
     private ScheduledExecutorService l2lbExecutor;
-    // Pattern for L2Lb key
-    private static final String L2LB_PATTERN = "^(L2LB\\(\\d*\\))$";
     // We need to listen for some events to properly installed the xconnect with l2lb
     private final L2LbListener l2LbListener = new InternalL2LbListener();
 
@@ -192,9 +193,12 @@
                 .register(KryoNamespaces.API)
                 .register(XconnectManager.class)
                 .register(XconnectKey.class)
+                .register(XconnectEndpoint.class)
+                .register(XconnectPortEndpoint.class)
+                .register(XconnectLoadBalancerEndpoint.class)
                 .register(VlanNextObjectiveStoreKey.class);
 
-        xconnectStore = storageService.<XconnectKey, Set<String>>consistentMapBuilder()
+        xconnectStore = storageService.<XconnectKey, Set<XconnectEndpoint>>consistentMapBuilder()
                 .withName("onos-sr-xconnect")
                 .withRelaxedReadConsistency()
                 .withSerializer(Serializer.using(serializer.build()))
@@ -257,11 +261,11 @@
     }
 
     @Override
-    public void addOrUpdateXconnect(DeviceId deviceId, VlanId vlanId, Set<String> ports) {
-        log.info("Adding or updating xconnect. deviceId={}, vlanId={}, ports={}",
-                 deviceId, vlanId, ports);
+    public void addOrUpdateXconnect(DeviceId deviceId, VlanId vlanId, Set<XconnectEndpoint> endpoints) {
+        log.info("Adding or updating xconnect. deviceId={}, vlanId={}, endpoints={}",
+                 deviceId, vlanId, endpoints);
         final XconnectKey key = new XconnectKey(deviceId, vlanId);
-        xconnectStore.put(key, ports);
+        xconnectStore.put(key, endpoints);
     }
 
     @Override
@@ -272,9 +276,9 @@
         xconnectStore.remove(key);
 
         // Cleanup multicasting support, if any.
-        srService.getPairDeviceId(deviceId).ifPresent(pairDeviceId -> {
-            cleanupL2MulticastRule(pairDeviceId, srService.getPairLocalPort(pairDeviceId).get(), vlanId, true);
-        });
+        srService.getPairDeviceId(deviceId).ifPresent(pairDeviceId ->
+            cleanupL2MulticastRule(pairDeviceId, srService.getPairLocalPort(pairDeviceId).get(), vlanId, true)
+        );
 
     }
 
@@ -287,16 +291,20 @@
 
     @Override
     public boolean hasXconnect(ConnectPoint cp) {
-        return getXconnects().stream().anyMatch(desc -> desc.key().deviceId().equals(cp.deviceId())
-                && desc.ports().contains(cp.port().toString())
+        return getXconnects().stream().anyMatch(desc ->
+                desc.key().deviceId().equals(cp.deviceId()) && desc.endpoints().stream().anyMatch(ep ->
+                        ep.type() == XconnectEndpoint.Type.PORT && ((XconnectPortEndpoint) ep).port().equals(cp.port())
+                )
         );
     }
 
     @Override
     public List<VlanId> getXconnectVlans(DeviceId deviceId, PortNumber port) {
         return getXconnects().stream()
-                .filter(desc -> desc.key().deviceId().equals(deviceId) && desc.ports().contains(port.toString()))
-                .map(desc -> desc.key().vlanId())
+                .filter(desc -> desc.key().deviceId().equals(deviceId) && desc.endpoints().stream().anyMatch(ep ->
+                        ep.type() == XconnectEndpoint.Type.PORT && ((XconnectPortEndpoint) ep).port().equals(port)))
+                .map(XconnectDesc::key)
+                .map(XconnectKey::vlanId)
                 .collect(Collectors.toList());
     }
 
@@ -329,12 +337,12 @@
         });
     }
 
-    private class XconnectMapListener implements MapEventListener<XconnectKey, Set<String>> {
+    private class XconnectMapListener implements MapEventListener<XconnectKey, Set<XconnectEndpoint>> {
         @Override
-        public void event(MapEvent<XconnectKey, Set<String>> event) {
+        public void event(MapEvent<XconnectKey, Set<XconnectEndpoint>> event) {
             XconnectKey key = event.key();
-            Set<String> ports = Versioned.valueOrNull(event.newValue());
-            Set<String> oldPorts = Versioned.valueOrNull(event.oldValue());
+            Set<XconnectEndpoint> ports = Versioned.valueOrNull(event.newValue());
+            Set<XconnectEndpoint> oldPorts = Versioned.valueOrNull(event.oldValue());
 
             switch (event.type()) {
                 case INSERT:
@@ -476,7 +484,7 @@
     private void init(DeviceId deviceId) {
         getXconnects().stream()
                 .filter(desc -> desc.key().deviceId().equals(deviceId))
-                .forEach(desc -> populateXConnect(desc.key(), desc.ports()));
+                .forEach(desc -> populateXConnect(desc.key(), desc.endpoints()));
     }
 
     private void cleanup(DeviceId deviceId) {
@@ -489,21 +497,21 @@
     /**
      * Populates XConnect groups and flows for given key.
      *
-     * @param key   XConnect key
-     * @param ports a set of ports to be cross-connected
+     * @param key       XConnect key
+     * @param endpoints a set of endpoints to be cross-connected
      */
-    private void populateXConnect(XconnectKey key, Set<String> ports) {
+    private void populateXConnect(XconnectKey key, Set<XconnectEndpoint> endpoints) {
         if (!isLocalLeader(key.deviceId())) {
             log.debug("Abort populating XConnect {}: {}", key, ERROR_NOT_LEADER);
             return;
         }
 
-        int nextId = populateNext(key, ports);
+        int nextId = populateNext(key, endpoints);
         if (nextId == -1) {
             log.warn("Fail to populateXConnect {}: {}", key, ERROR_NEXT_ID);
             return;
         }
-        populateFilter(key, ports);
+        populateFilter(key, endpoints);
         populateFwd(key, nextId);
         populateAcl(key);
     }
@@ -511,45 +519,45 @@
     /**
      * Populates filtering objectives for given XConnect.
      *
-     * @param key   XConnect store key
-     * @param ports XConnect ports
+     * @param key       XConnect store key
+     * @param endpoints XConnect endpoints
      */
-    private void populateFilter(XconnectKey key, Set<String> ports) {
+    private void populateFilter(XconnectKey key, Set<XconnectEndpoint> endpoints) {
         // FIXME Improve the logic
         //       If L2 load balancer is not involved, use filtered port. Otherwise, use unfiltered port.
         //       The purpose is to make sure existing XConnect logic can still work on a configured port.
-        boolean filtered = ports.stream()
-                .map(p -> getNextTreatment(key.deviceId(), p, false))
+        boolean filtered = endpoints.stream()
+                .map(ep -> getNextTreatment(key.deviceId(), ep, false))
                 .allMatch(t -> t.type().equals(NextTreatment.Type.TREATMENT));
 
-        ports.stream()
-                .map(p -> getPhysicalPorts(key.deviceId(), p))
+        endpoints.stream()
+                .map(ep -> getPhysicalPorts(key.deviceId(), ep))
                 .flatMap(Set::stream).forEach(port -> {
-            FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port, filtered);
-            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));
-        });
+                    FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port, filtered);
+                    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));
+                });
     }
 
     /**
      * Populates next objectives for given XConnect.
      *
-     * @param key   XConnect store key
-     * @param ports XConnect ports
+     * @param key       XConnect store key
+     * @param endpoints XConnect endpoints
      * @return next id
      */
-    private int populateNext(XconnectKey key, Set<String> ports) {
+    private int populateNext(XconnectKey key, Set<XconnectEndpoint> endpoints) {
         int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
         if (nextId != -1) {
             log.debug("NextObj for {} found, id={}", key, nextId);
             return nextId;
         } else {
-            NextObjective.Builder nextObjBuilder = nextObjBuilder(key, ports);
+            NextObjective.Builder nextObjBuilder = nextObjBuilder(key, endpoints);
             if (nextObjBuilder == null) {
                 log.warn("Fail to populate {}: {}", key, ERROR_NEXT_OBJ_BUILDER);
                 return -1;
@@ -602,61 +610,64 @@
     /**
      * Revokes XConnect groups and flows for given key.
      *
-     * @param key   XConnect key
-     * @param ports XConnect ports
+     * @param key       XConnect key
+     * @param endpoints XConnect endpoints
      */
-    private void revokeXConnect(XconnectKey key, Set<String> ports) {
+    private void revokeXConnect(XconnectKey key, Set<XconnectEndpoint> endpoints) {
         if (!isLocalLeader(key.deviceId())) {
             log.debug("Abort revoking XConnect {}: {}", key, ERROR_NOT_LEADER);
             return;
         }
 
-        revokeFilter(key, ports);
+        revokeFilter(key, endpoints);
         int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
         if (nextId != -1) {
             revokeFwd(key, nextId, null);
-            revokeNext(key, ports, nextId, null);
+            revokeNext(key, endpoints, nextId, null);
         } else {
             log.warn("NextObj for {} does not exist in the store.", key);
         }
-        revokeFilter(key, ports);
+        revokeFilter(key, endpoints);
         revokeAcl(key);
     }
 
     /**
      * Revokes filtering objectives for given XConnect.
      *
-     * @param key   XConnect store key
-     * @param ports XConnect ports
+     * @param key       XConnect store key
+     * @param endpoints XConnect endpoints
      */
-    private void revokeFilter(XconnectKey key, Set<String> ports) {
+    private void revokeFilter(XconnectKey key, Set<XconnectEndpoint> endpoints) {
         // FIXME Improve the logic
         //       If L2 load balancer is not involved, use filtered port. Otherwise, use unfiltered port.
         //       The purpose is to make sure existing XConnect logic can still work on a configured port.
-        Set<Set<PortNumber>> portsSet = ports.stream()
-                .map(p -> getPhysicalPorts(key.deviceId(), p)).collect(Collectors.toSet());
-        boolean filtered = portsSet.stream().allMatch(s -> s.size() == 1);
-        portsSet.stream().flatMap(Set::stream).forEach(port -> {
-            FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port, filtered);
-            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));
-        });
+        boolean filtered = endpoints.stream()
+                .map(ep -> getNextTreatment(key.deviceId(), ep, false))
+                .allMatch(t -> t.type().equals(NextTreatment.Type.TREATMENT));
+
+        endpoints.stream()
+                .map(ep -> getPhysicalPorts(key.deviceId(), ep)).
+                flatMap(Set::stream).forEach(port -> {
+                    FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port, filtered);
+                    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 ports      ports in the XConnect
+     * @param endpoints  XConnect endpoints
      * @param nextId     next objective id
      * @param nextFuture completable future for this next objective operation
      */
-    private void revokeNext(XconnectKey key, Set<String> ports, int nextId,
+    private void revokeNext(XconnectKey key, Set<XconnectEndpoint> endpoints, int nextId,
                             CompletableFuture<ObjectiveError> nextFuture) {
         ObjectiveContext context = new ObjectiveContext() {
             @Override
@@ -677,18 +688,18 @@
             }
         };
 
-        NextObjective.Builder nextObjBuilder = nextObjBuilder(key, ports, nextId);
+        NextObjective.Builder nextObjBuilder = nextObjBuilder(key, endpoints, nextId);
         if (nextObjBuilder == null) {
             log.warn("Fail to revokeNext {}: {}", key, ERROR_NEXT_OBJ_BUILDER);
             return;
         }
         // Release the L2Lbs if present
-        ports.forEach(port -> {
-            if (isL2LbKey(port)) {
-                String l2LbKey = port.substring("L2LB(".length(), port.length() - 1);
-                l2LbService.release(new L2LbId(key.deviceId(), Integer.parseInt(l2LbKey)), appId);
-            }
-        });
+        endpoints.stream()
+                .filter(endpoint -> endpoint.type() == XconnectEndpoint.Type.LOAD_BALANCER)
+                .forEach(endpoint -> {
+                    String l2LbKey = String.valueOf(((XconnectLoadBalancerEndpoint) endpoint).key());
+                    l2LbService.release(new L2LbId(key.deviceId(), Integer.parseInt(l2LbKey)), appId);
+                });
         flowObjectiveService.next(key.deviceId(), nextObjBuilder.remove(context));
         xconnectNextObjStore.remove(key);
     }
@@ -739,12 +750,12 @@
     /**
      * Updates XConnect groups and flows for given key.
      *
-     * @param key       XConnect key
-     * @param prevPorts previous XConnect ports
-     * @param ports     new XConnect ports
+     * @param key           XConnect key
+     * @param prevEndpoints previous XConnect endpoints
+     * @param endpoints     new XConnect endpoints
      */
-    private void updateXConnect(XconnectKey key, Set<String> prevPorts,
-                                Set<String> ports) {
+    private void updateXConnect(XconnectKey key, Set<XconnectEndpoint> prevEndpoints,
+                                Set<XconnectEndpoint> endpoints) {
         if (!isLocalLeader(key.deviceId())) {
             log.debug("Abort updating XConnect {}: {}", key, ERROR_NOT_LEADER);
             return;
@@ -753,11 +764,11 @@
         //       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)));
+        prevEndpoints.stream().filter(prevEndpoint -> !endpoints.contains(prevEndpoint)).forEach(prevEndpoint ->
+                revokeFilter(key, ImmutableSet.of(prevEndpoint)));
         // install new filter
-        ports.stream().filter(port -> !prevPorts.contains(port)).forEach(port ->
-                populateFilter(key, ImmutableSet.of(port)));
+        endpoints.stream().filter(endpoint -> !prevEndpoints.contains(endpoint)).forEach(endpoint ->
+                populateFilter(key, ImmutableSet.of(endpoint)));
 
         CompletableFuture<ObjectiveError> fwdFuture = new CompletableFuture<>();
         CompletableFuture<ObjectiveError> nextFuture = new CompletableFuture<>();
@@ -769,14 +780,14 @@
             fwdFuture.thenAcceptAsync(fwdStatus -> {
                 if (fwdStatus == null) {
                     log.debug("Fwd removed. Now remove group {}", key);
-                    revokeNext(key, prevPorts, nextId, nextFuture);
+                    revokeNext(key, prevEndpoints, nextId, nextFuture);
                 }
             });
 
             nextFuture.thenAcceptAsync(nextStatus -> {
                 if (nextStatus == null) {
                     log.debug("Installing new group and flow for {}", key);
-                    int newNextId = populateNext(key, ports);
+                    int newNextId = populateNext(key, endpoints);
                     if (newNextId == -1) {
                         log.warn("Fail to updateXConnect {}: {}", key, ERROR_NEXT_ID);
                         return;
@@ -792,12 +803,12 @@
     /**
      * Creates a next objective builder for XConnect with given nextId.
      *
-     * @param key     XConnect key
-     * @param ports   ports or L2 load balancer key
+     * @param key       XConnect key
+     * @param endpoints XConnect endpoints
      * @param nextId  next objective id
      * @return next objective builder
      */
-    private NextObjective.Builder nextObjBuilder(XconnectKey key, Set<String> ports, int nextId) {
+    private NextObjective.Builder nextObjBuilder(XconnectKey key, Set<XconnectEndpoint> endpoints, int nextId) {
         TrafficSelector metadata =
                 DefaultTrafficSelector.builder().matchVlanId(key.vlanId()).build();
         NextObjective.Builder nextObjBuilder = DefaultNextObjective
@@ -805,13 +816,13 @@
                 .withType(NextObjective.Type.BROADCAST).fromApp(appId)
                 .withMeta(metadata);
 
-        for (String port : ports) {
-            NextTreatment nextTreatment = getNextTreatment(key.deviceId(), port, true);
+        for (XconnectEndpoint endpoint : endpoints) {
+            NextTreatment nextTreatment = getNextTreatment(key.deviceId(), endpoint, true);
             if (nextTreatment == null) {
                 // If a L2Lb is used in the XConnect - putting on hold
-                if (isL2LbKey(port)) {
+                if (endpoint.type() == XconnectEndpoint.Type.LOAD_BALANCER) {
                     log.warn("Unable to create nextObj. L2Lb not ready");
-                    String l2LbKey = port.substring("L2LB(".length(), port.length() - 1);
+                    String l2LbKey = String.valueOf(((XconnectLoadBalancerEndpoint) endpoint).key());
                     l2LbCache.asMap().putIfAbsent(new L2LbId(key.deviceId(), Integer.parseInt(l2LbKey)),
                                                   key);
                 } else {
@@ -828,13 +839,13 @@
     /**
      * Creates a next objective builder for XConnect.
      *
-     * @param key   XConnect key
-     * @param ports set of XConnect ports
+     * @param key       XConnect key
+     * @param endpoints Xconnect endpoints
      * @return next objective builder
      */
-    private NextObjective.Builder nextObjBuilder(XconnectKey key, Set<String> ports) {
+    private NextObjective.Builder nextObjBuilder(XconnectKey key, Set<XconnectEndpoint> endpoints) {
         int nextId = flowObjectiveService.allocateNextId();
-        return nextObjBuilder(key, ports, nextId);
+        return nextObjBuilder(key, endpoints, nextId);
     }
 
 
@@ -1229,64 +1240,39 @@
         return true;
     }
 
-    private Set<PortNumber> getPhysicalPorts(DeviceId deviceId, String port) {
-        // If port is numeric, treat it as regular port.
-        // Otherwise try to parse it as load balancer key and get the physical port the LB maps to.
-        try {
-            return Sets.newHashSet(PortNumber.portNumber(Integer.parseInt(port)));
-        } catch (NumberFormatException e) {
-            log.debug("Port {} is not numeric. Try to parse it as load balancer key", port);
+    private Set<PortNumber> getPhysicalPorts(DeviceId deviceId, XconnectEndpoint endpoint) {
+        if (endpoint.type() == XconnectEndpoint.Type.PORT) {
+            PortNumber port = ((XconnectPortEndpoint) endpoint).port();
+            return Sets.newHashSet(port);
         }
-
-        String l2LbKey = port.substring("L2LB(".length(), port.length() - 1);
-        L2LbId l2LbId = new L2LbId(deviceId, Integer.parseInt(l2LbKey));
-        try {
-            return Sets.newHashSet(l2LbService.getL2Lb(l2LbId).ports());
-        } catch (NumberFormatException e) {
-            log.debug("Port {} is not load balancer key either. Ignore", port);
-        } catch (NullPointerException e) {
-            log.debug("L2 load balancer {} not found. Ignore", l2LbKey);
+        if (endpoint.type() == XconnectEndpoint.Type.LOAD_BALANCER) {
+            L2LbId l2LbId = new L2LbId(deviceId, ((XconnectLoadBalancerEndpoint) endpoint).key());
+            Set<PortNumber> ports = l2LbService.getL2Lb(l2LbId).ports();
+            return Sets.newHashSet(ports);
         }
-
         return Sets.newHashSet();
     }
 
-    private NextTreatment getNextTreatment(DeviceId deviceId, String port, boolean reserve) {
-        // If port is numeric, treat it as regular port.
-        // Otherwise try to parse it as load balancer key and get the physical port the LB maps to.
-        try {
-            PortNumber portNumber = PortNumber.portNumber(Integer.parseInt(port));
-            return DefaultNextTreatment.of(DefaultTrafficTreatment.builder().setOutput(portNumber).build());
-        } catch (NumberFormatException e) {
-            log.debug("Port {} is not numeric. Try to parse it as load balancer key", port);
+    private NextTreatment getNextTreatment(DeviceId deviceId, XconnectEndpoint endpoint, boolean reserve) {
+        if (endpoint.type() == XconnectEndpoint.Type.PORT) {
+            PortNumber port = ((XconnectPortEndpoint) endpoint).port();
+            return DefaultNextTreatment.of(DefaultTrafficTreatment.builder().setOutput(port).build());
         }
-
-        String l2LbKey = port.substring("L2LB(".length(), port.length() - 1);
-        try {
-            L2LbId l2LbId = new L2LbId(deviceId, Integer.parseInt(l2LbKey));
-            NextTreatment idNextTreatment =  IdNextTreatment.of(
-                    l2LbService.getL2LbNext(l2LbId));
+        if (endpoint.type() == XconnectEndpoint.Type.LOAD_BALANCER) {
+            L2LbId l2LbId = new L2LbId(deviceId, ((XconnectLoadBalancerEndpoint) endpoint).key());
+            NextTreatment idNextTreatment =  IdNextTreatment.of(l2LbService.getL2LbNext(l2LbId));
             // Reserve only one time during next objective creation
             if (reserve) {
-                if (!l2LbService.reserve(new L2LbId(deviceId, Integer.parseInt(l2LbKey)), appId)) {
+                if (!l2LbService.reserve(l2LbId, appId)) {
                     log.warn("Reservation failed for {}", l2LbId);
                     idNextTreatment = null;
                 }
             }
             return idNextTreatment;
-        } catch (NumberFormatException e) {
-            log.debug("Port {} is not load balancer key either. Ignore", port);
-        } catch (NullPointerException e) {
-            log.debug("L2 load balancer {} not found. Ignore", l2LbKey);
         }
-
         return null;
     }
 
-    private boolean isL2LbKey(String l2LbKey) {
-        return l2LbKey.matches(L2LB_PATTERN);
-    }
-
     private class InternalL2LbListener implements L2LbListener {
         // Populate xconnect once l2lb is available
         @Override
@@ -1309,12 +1295,12 @@
         }
         log.debug("Dequeue {}", l2LbId);
         l2LbCache.invalidate(l2LbId);
-        Set<String> ports = Versioned.valueOrNull(xconnectStore.get(xconnectKey));
-        if (ports == null || ports.isEmpty()) {
-            log.warn("Ports not found for XConnect {}", xconnectKey);
+        Set<XconnectEndpoint> endpoints = Versioned.valueOrNull(xconnectStore.get(xconnectKey));
+        if (endpoints == null || endpoints.isEmpty()) {
+            log.warn("Endpoints not found for XConnect {}", xconnectKey);
             return;
         }
-        populateXConnect(xconnectKey, ports);
+        populateXConnect(xconnectKey, endpoints);
         log.trace("L2Lb cache size {}", l2LbCache.size());
     }