Extends L2LbManager

- reacts to device events
- updates/removes l2lb
- implements bookeeping of the l2lb
- extends L2LbEvent to proper notify other components

Change-Id: I944fe6415324d71c361bafc4146dd176493f2dc7
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2Lb.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2Lb.java
index 3c377ff..633af1e 100644
--- a/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2Lb.java
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2Lb.java
@@ -70,6 +70,15 @@
         return mode;
     }
 
+    /**
+     * Gets L2 load balancer data.
+     *
+     * @return L2 load balancer data
+     */
+    public L2LbData data() {
+        return new L2LbData(l2LbId);
+    }
+
     @Override
     public int hashCode() {
         return Objects.hash(l2LbId, ports, mode);
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbAdminService.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbAdminService.java
index 076ed36..b429fe1 100644
--- a/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbAdminService.java
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbAdminService.java
@@ -40,7 +40,7 @@
      *
      * @param deviceId Device ID
      * @param key L2 load balancer key
-     * @return L2 load balancer that is removed
+     * @return L2 load balancer that is removed or null if it was not possible
      */
     L2Lb remove(DeviceId deviceId, int key);
 
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbData.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbData.java
new file mode 100644
index 0000000..d50dbbd
--- /dev/null
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbData.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2018-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.l2lb.api;
+
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Represents L2 load balancer event data.
+ */
+public class L2LbData {
+
+    // We exchange only id and nextid in the events
+    private L2LbId l2LbId;
+    private int nextId;
+
+    /**
+     * Constructs a L2 load balancer data.
+     *
+     * @param l2LbId L2 load balancer ID
+     */
+    public L2LbData(L2LbId l2LbId) {
+        this.l2LbId = l2LbId;
+        this.nextId = -1;
+    }
+
+    /**
+     * Constructs a L2 load balancer data.
+     *
+     * @param l2LbId L2 load balancer ID
+     * @param nextId L2 load balancer next id
+     */
+    public L2LbData(L2LbId l2LbId, int nextId) {
+        this.l2LbId = l2LbId;
+        this.nextId = nextId;
+    }
+
+    /**
+     * Gets L2 load balancer ID.
+     *
+     * @return L2 load balancer ID
+     */
+    public L2LbId l2LbId() {
+        return l2LbId;
+    }
+
+    /**
+     * Gets L2 load balancer next id.
+     *
+     * @return L2 load balancer next id
+     */
+    public int nextId() {
+        return nextId;
+    }
+
+    /**
+     * Sets L2 load balancer next id.
+     *
+     * @param nextId L2 load balancer next id
+     */
+    public void setNextId(int nextId) {
+        this.nextId = nextId;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(l2LbId, nextId);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof L2LbData)) {
+            return false;
+        }
+        final L2LbData other = (L2LbData) obj;
+
+        return Objects.equals(this.l2LbId, other.l2LbId) &&
+                this.nextId == other.nextId;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(getClass())
+                .add("l2LbId", l2LbId)
+                .add("nextId", nextId)
+                .toString();
+    }
+}
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbEvent.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbEvent.java
index f0ac847..a0ce9ce 100644
--- a/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbEvent.java
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbEvent.java
@@ -22,9 +22,9 @@
 
 import static com.google.common.base.MoreObjects.toStringHelper;
 
-public class L2LbEvent extends AbstractEvent<L2LbEvent.Type, L2Lb> {
+public class L2LbEvent extends AbstractEvent<L2LbEvent.Type, L2LbData> {
 
-    private L2Lb prevSubject;
+    private L2LbData prevSubject;
 
     /**
      * L2 load balancer event type.
@@ -32,7 +32,10 @@
     public enum Type {
         ADDED,
         REMOVED,
-        UPDATED
+        UPDATED,
+        INSTALLED,
+        UNINSTALLED,
+        FAILED
     }
 
     /**
@@ -42,7 +45,7 @@
      * @param subject current L2 load balancer information
      * @param prevSubject previous L2 load balancer information
      */
-    public L2LbEvent(Type type, L2Lb subject, L2Lb prevSubject) {
+    public L2LbEvent(Type type, L2LbData subject, L2LbData prevSubject) {
         super(type, subject);
         this.prevSubject = prevSubject;
     }
@@ -52,7 +55,7 @@
      *
      * @return previous subject
      */
-    public L2Lb prevSubject() {
+    public L2LbData prevSubject() {
         return prevSubject;
     }
 
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbService.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbService.java
index 8779192..db5f20c 100644
--- a/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbService.java
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbService.java
@@ -16,6 +16,7 @@
 
 package org.onosproject.l2lb.api;
 
+import org.onosproject.core.ApplicationId;
 import org.onosproject.event.ListenerService;
 import org.onosproject.net.DeviceId;
 
@@ -56,4 +57,43 @@
      * @return next ID
      */
     int getL2LbNexts(DeviceId deviceId, int key);
+
+    /**
+     * Reserves a l2 load balancer. Only one application
+     * at time can reserve a given l2 load balancer.
+     *
+     * @param l2LbId the l2 load balancer id
+     * @param appId the application id
+     * @return true if reservation was successful false otherwise
+     */
+    boolean reserve(L2LbId l2LbId, ApplicationId appId);
+
+    /**
+     * Releases a l2 load balancer. Once released
+     * by the owner the l2 load balancer is eligible
+     * for removal.
+     *
+     * @param l2LbId the l2 load balancer id
+     * @param appId the application id
+     * @return true if release was successful false otherwise
+     */
+    boolean release(L2LbId l2LbId, ApplicationId appId);
+
+    /**
+     * Gets reservation of a l2 load balancer. Only one application
+     * at time can reserve a given l2 load balancer.
+     *
+     * @param l2LbId the l2 load balancer id
+     * @return the id of the application using the l2 load balancer
+     */
+    ApplicationId getReservation(L2LbId l2LbId);
+
+    /**
+     * Gets l2 load balancer reservations. Only one application
+     * at time can reserve a given l2 load balancer.
+     *
+     * @return reservations of the l2 load balancer resources
+     */
+    Map<L2LbId, ApplicationId> getReservations();
+
 }
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/app/L2LbManager.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/app/L2LbManager.java
index 1f58960..3a195dc 100644
--- a/apps/l2lb/src/main/java/org/onosproject/l2lb/app/L2LbManager.java
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/app/L2LbManager.java
@@ -16,12 +16,15 @@
 
 package org.onosproject.l2lb.app;
 
-
 import com.google.common.collect.Sets;
 import org.onlab.util.KryoNamespace;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.l2lb.api.L2Lb;
+import org.onosproject.l2lb.api.L2LbData;
 import org.onosproject.l2lb.api.L2LbEvent;
 import org.onosproject.l2lb.api.L2LbAdminService;
 import org.onosproject.l2lb.api.L2LbId;
@@ -31,6 +34,8 @@
 import org.onosproject.mastership.MastershipService;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
@@ -94,6 +99,12 @@
     private MastershipService mastershipService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    private LeadershipService leadershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    private ClusterService clusterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
     private DeviceService deviceService;
 
     private static final Logger log = getLogger(L2LbManager.class);
@@ -102,22 +113,33 @@
     private ApplicationId appId;
     private ConsistentMap<L2LbId, L2Lb> l2LbStore;
     private ConsistentMap<L2LbId, Integer> l2LbNextStore;
+    // TODO Evaluate if ResourceService is a better option
+    private ConsistentMap<L2LbId, ApplicationId> l2LbResStore;
     private Set<L2LbListener> listeners = Sets.newConcurrentHashSet();
 
     private ExecutorService l2LbEventExecutor;
     private ExecutorService l2LbProvExecutor;
+    private ExecutorService deviceEventExecutor;
+
     private MapEventListener<L2LbId, L2Lb> l2LbStoreListener;
     // TODO build CLI to view and clear the next store
     private MapEventListener<L2LbId, Integer> l2LbNextStoreListener;
+    private MapEventListener<L2LbId, ApplicationId> l2LbResStoreListener;
+    private final DeviceListener deviceListener = new InternalDeviceListener();
 
     @Activate
     public void activate() {
         appId = coreService.registerApplication(APP_NAME);
 
-        l2LbEventExecutor = Executors.newSingleThreadExecutor(groupedThreads("l2lb-event", "%d", log));
-        l2LbProvExecutor = Executors.newSingleThreadExecutor(groupedThreads("l2lb-prov", "%d", log));
+        l2LbEventExecutor = Executors.newSingleThreadExecutor(
+                groupedThreads("l2lb-event", "%d", log));
+        l2LbProvExecutor = Executors.newSingleThreadExecutor(
+                groupedThreads("l2lb-prov", "%d", log));
+        deviceEventExecutor = Executors.newSingleThreadScheduledExecutor(
+                groupedThreads("l2lb-dev-event", "%d", log));
         l2LbStoreListener = new L2LbStoreListener();
         l2LbNextStoreListener = new L2LbNextStoreListener();
+        l2LbResStoreListener = new L2LbResStoreListener();
 
         KryoNamespace serializer = KryoNamespace.newBuilder()
                 .register(KryoNamespaces.API)
@@ -137,6 +159,14 @@
                 .withSerializer(Serializer.using(serializer))
                 .build();
         l2LbNextStore.addListener(l2LbNextStoreListener);
+        l2LbResStore = storageService.<L2LbId, ApplicationId>consistentMapBuilder()
+                .withName("onos-l2lb-res-store")
+                .withRelaxedReadConsistency()
+                .withSerializer(Serializer.using(serializer))
+                .build();
+        l2LbResStore.addListener(l2LbResStoreListener);
+
+        deviceService.addListener(deviceListener);
 
         log.info("Started");
     }
@@ -147,6 +177,8 @@
         l2LbNextStore.removeListener(l2LbNextStoreListener);
 
         l2LbEventExecutor.shutdown();
+        l2LbProvExecutor.shutdown();
+        deviceEventExecutor.shutdown();
 
         log.info("Stopped");
     }
@@ -171,8 +203,15 @@
     @Override
     public L2Lb remove(DeviceId deviceId, int key) {
         L2LbId l2LbId = new L2LbId(deviceId, key);
-        log.debug("Removing {} from L2 load balancer store", l2LbId);
-        return Versioned.valueOrNull(l2LbStore.remove(l2LbId));
+        ApplicationId reservation = Versioned.valueOrNull(l2LbResStore.get(l2LbId));
+        // Remove only if it is not used - otherwise it is necessary to release first
+        if (reservation == null) {
+            log.debug("Removing {} from L2 load balancer store", l2LbId);
+            return Versioned.valueOrNull(l2LbStore.remove(l2LbId));
+        }
+        log.warn("Removal {} from L2 load balancer store was not possible " +
+                          "due to a previous reservation", l2LbId);
+        return null;
     }
 
     @Override
@@ -195,23 +234,62 @@
         return Versioned.valueOrNull(l2LbNextStore.get(new L2LbId(deviceId, key)));
     }
 
+    @Override
+    public boolean reserve(L2LbId l2LbId, ApplicationId appId) {
+        // Check if the resource is available
+        ApplicationId reservation = Versioned.valueOrNull(l2LbResStore.get(l2LbId));
+        L2Lb l2Lb = Versioned.valueOrNull(l2LbStore.get(l2LbId));
+        if (reservation == null && l2Lb != null) {
+            log.debug("Reserving {} -> {} into L2 load balancer reservation store", l2LbId, appId);
+            return l2LbResStore.put(l2LbId, appId) == null;
+        } else if (reservation != null && reservation.equals(appId)) {
+            // App try to reserve the resource a second time
+            log.debug("Already reserved {} -> {} skip reservation", l2LbId, appId);
+            return true;
+        }
+        log.warn("Reservation failed {} -> {}", l2LbId, appId);
+        return false;
+    }
+
+    @Override
+    public boolean release(L2LbId l2LbId, ApplicationId appId) {
+        // Check if the resource is reserved
+        ApplicationId reservation = Versioned.valueOrNull(l2LbResStore.get(l2LbId));
+        if (reservation != null && reservation.equals(appId)) {
+            log.debug("Removing {} -> {} from L2 load balancer reservation store", l2LbId, appId);
+            return l2LbResStore.remove(l2LbId) != null;
+        }
+        log.warn("Release failed {} -> {}", l2LbId, appId);
+        return false;
+    }
+
+    @Override
+    public ApplicationId getReservation(L2LbId l2LbId) {
+        return Versioned.valueOrNull(l2LbResStore.get(l2LbId));
+    }
+
+    @Override
+    public Map<L2LbId, ApplicationId> getReservations() {
+        return l2LbResStore.asJavaMap();
+    }
+
     private class L2LbStoreListener implements MapEventListener<L2LbId, L2Lb> {
         public void event(MapEvent<L2LbId, L2Lb> event) {
             switch (event.type()) {
                 case INSERT:
                     log.debug("L2Lb {} insert new={}, old={}", event.key(), event.newValue(), event.oldValue());
-                    post(new L2LbEvent(L2LbEvent.Type.ADDED, event.newValue().value(), null));
+                    post(new L2LbEvent(L2LbEvent.Type.ADDED, event.newValue().value().data(), null));
                     populateL2Lb(event.newValue().value());
                     break;
                 case REMOVE:
                     log.debug("L2Lb {} remove new={}, old={}", event.key(), event.newValue(), event.oldValue());
-                    post(new L2LbEvent(L2LbEvent.Type.REMOVED, null, event.oldValue().value()));
+                    post(new L2LbEvent(L2LbEvent.Type.REMOVED, null, event.oldValue().value().data()));
                     revokeL2Lb(event.oldValue().value());
                     break;
                 case UPDATE:
                     log.debug("L2Lb {} update new={}, old={}", event.key(), event.newValue(), event.oldValue());
-                    post(new L2LbEvent(L2LbEvent.Type.UPDATED, event.newValue().value(),
-                            event.oldValue().value()));
+                    post(new L2LbEvent(L2LbEvent.Type.UPDATED, event.newValue().value().data(),
+                            event.oldValue().value().data()));
                     updateL2Lb(event.newValue().value(), event.oldValue().value());
                     break;
                 default:
@@ -238,67 +316,159 @@
         }
     }
 
+    private class L2LbResStoreListener implements MapEventListener<L2LbId, ApplicationId> {
+        public void event(MapEvent<L2LbId, ApplicationId> event) {
+            switch (event.type()) {
+                case INSERT:
+                    log.debug("L2Lb reservation {} insert new={}, old={}", event.key(), event.newValue(),
+                              event.oldValue());
+                    break;
+                case REMOVE:
+                    log.debug("L2Lb reservation {} remove new={}, old={}", event.key(), event.newValue(),
+                              event.oldValue());
+                    break;
+                case UPDATE:
+                    log.debug("L2Lb reservation {} update new={}, old={}", event.key(), event.newValue(),
+                              event.oldValue());
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    private class InternalDeviceListener implements DeviceListener {
+        // We want to manage only a subset of events and if we are the leader
+        @Override
+        public void event(DeviceEvent event) {
+            deviceEventExecutor.execute(() -> {
+                DeviceId deviceId = event.subject().id();
+                if (!isLocalLeader(deviceId)) {
+                    log.debug("Not the leader of {}. Skip event {}", deviceId, event);
+                    return;
+                }
+                // Populate or revoke according to the device availability
+                if (deviceService.isAvailable(deviceId)) {
+                    init(deviceId);
+                } else {
+                    cleanup(deviceId);
+                }
+            });
+        }
+        // Some events related to the devices are skipped
+        @Override
+        public boolean isRelevant(DeviceEvent event) {
+            return event.type() == DeviceEvent.Type.DEVICE_ADDED ||
+                    event.type() == DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED ||
+                    event.type() == DeviceEvent.Type.DEVICE_UPDATED;
+        }
+    }
+
     private void post(L2LbEvent l2LbEvent) {
         l2LbEventExecutor.execute(() -> {
             for (L2LbListener l : listeners) {
-                l.event(l2LbEvent);
+                if (l.isRelevant(l2LbEvent)) {
+                    l.event(l2LbEvent);
+                }
             }
         });
     }
 
-    // TODO repopulate when device reconnect
+    private void init(DeviceId deviceId) {
+        l2LbStore.entrySet().stream()
+                .filter(l2lbentry -> l2lbentry.getKey().deviceId().equals(deviceId))
+                .forEach(l2lbentry -> populateL2Lb(l2lbentry.getValue().value()));
+    }
+
+    private void cleanup(DeviceId deviceId) {
+        l2LbStore.entrySet().stream()
+                .filter(entry -> entry.getKey().deviceId().equals(deviceId))
+                .forEach(entry -> l2LbNextStore.remove(entry.getKey()));
+        log.debug("{} is removed from l2LbNextObjStore", deviceId);
+    }
+
     private void populateL2Lb(L2Lb l2Lb) {
         DeviceId deviceId = l2Lb.l2LbId().deviceId();
-        if (!mastershipService.isLocalMaster(deviceId)) {
-            log.debug("Not the master of {}. Skip populateL2Lb {}", deviceId, l2Lb.l2LbId());
+        if (!isLocalLeader(deviceId)) {
+            log.debug("Not the leader of {}. Skip populateL2Lb {}", deviceId, l2Lb.l2LbId());
             return;
         }
 
         l2LbProvExecutor.execute(() -> {
-            L2LbObjectiveContext context = new L2LbObjectiveContext(l2Lb.l2LbId());
-            NextObjective nextObj = nextObjBuilder(l2Lb.l2LbId(), l2Lb.ports()).add(context);
-
-            flowObjService.next(deviceId, nextObj);
-            l2LbNextStore.put(l2Lb.l2LbId(), nextObj.id());
+            Integer nextid = Versioned.valueOrNull(l2LbNextStore.get(l2Lb.l2LbId()));
+            if (nextid == null) {
+                // Build a new context and new next objective
+                L2LbObjectiveContext context = new L2LbObjectiveContext(l2Lb.l2LbId());
+                NextObjective nextObj = nextObjBuilder(l2Lb.l2LbId(), l2Lb.ports(), nextid).add(context);
+                // Finally submit, store, and register the resource
+                flowObjService.next(deviceId, nextObj);
+                l2LbNextStore.put(l2Lb.l2LbId(), nextObj.id());
+            } else {
+                log.info("NextObj for {} already exists. Skip populateL2Lb", l2Lb.l2LbId());
+            }
         });
     }
 
     private void revokeL2Lb(L2Lb l2Lb) {
         DeviceId deviceId = l2Lb.l2LbId().deviceId();
-        if (!mastershipService.isLocalMaster(deviceId)) {
-            log.debug("Not the master of {}. Skip revokeL2Lb {}", deviceId, l2Lb.l2LbId());
+        if (!isLocalLeader(deviceId)) {
+            log.debug("Not the leader of {}. Skip revokeL2Lb {}", deviceId, l2Lb.l2LbId());
             return;
         }
 
         l2LbProvExecutor.execute(() -> {
-            l2LbNextStore.remove(l2Lb.l2LbId());
-            // NOTE group is not removed and we rely on the garbage collection mechanism
+            Integer nextid = Versioned.valueOrNull(l2LbNextStore.get(l2Lb.l2LbId()));
+            if (nextid != null) {
+                // Build a new context and remove old next objective
+                L2LbObjectiveContext context = new L2LbObjectiveContext(l2Lb.l2LbId());
+                NextObjective nextObj = nextObjBuilder(l2Lb.l2LbId(), l2Lb.ports(), nextid).remove(context);
+                // Finally submit and invalidate the store
+                flowObjService.next(deviceId, nextObj);
+                l2LbNextStore.remove(l2Lb.l2LbId());
+            } else {
+                log.info("NextObj for {} does not exist. Skip revokeL2Lb", l2Lb.l2LbId());
+            }
         });
     }
 
     private void updateL2Lb(L2Lb newL2Lb, L2Lb oldL2Lb) {
         DeviceId deviceId = newL2Lb.l2LbId().deviceId();
-        if (!mastershipService.isLocalMaster(deviceId)) {
-            log.debug("Not the master of {}. Skip updateL2Lb {}", deviceId, newL2Lb.l2LbId());
+        if (!isLocalLeader(deviceId)) {
+            log.debug("Not the leader of {}. Skip updateL2Lb {}", deviceId, newL2Lb.l2LbId());
             return;
         }
 
         l2LbProvExecutor.execute(() -> {
-            L2LbObjectiveContext context = new L2LbObjectiveContext(newL2Lb.l2LbId());
-            Set<PortNumber> portsToBeAdded = Sets.difference(newL2Lb.ports(), oldL2Lb.ports());
-            Set<PortNumber> portsToBeRemoved = Sets.difference(oldL2Lb.ports(), newL2Lb.ports());
+            Integer nextid = Versioned.valueOrNull(l2LbNextStore.get(newL2Lb.l2LbId()));
+            if (nextid != null) {
+                // Compute modifications and context
+                L2LbObjectiveContext context = new L2LbObjectiveContext(newL2Lb.l2LbId());
+                Set<PortNumber> portsToBeAdded = Sets.difference(newL2Lb.ports(), oldL2Lb.ports());
+                Set<PortNumber> portsToBeRemoved = Sets.difference(oldL2Lb.ports(), newL2Lb.ports());
+                // and send them to the flowobj subsystem
+                if (!portsToBeAdded.isEmpty()) {
+                    flowObjService.next(deviceId, nextObjBuilder(newL2Lb.l2LbId(), portsToBeAdded, nextid)
+                            .addToExisting(context));
+                } else {
+                    log.debug("NextObj for {} nothing to add", newL2Lb.l2LbId());
 
-            flowObjService.next(deviceId, nextObjBuilder(newL2Lb.l2LbId(), portsToBeAdded).addToExisting(context));
-            flowObjService.next(deviceId, nextObjBuilder(newL2Lb.l2LbId(), portsToBeRemoved)
-                    .removeFromExisting(context));
+                }
+                if (!portsToBeRemoved.isEmpty()) {
+                    flowObjService.next(deviceId, nextObjBuilder(newL2Lb.l2LbId(), portsToBeRemoved, nextid)
+                            .removeFromExisting(context));
+                } else {
+                    log.debug("NextObj for {} nothing to remove", newL2Lb.l2LbId());
+                }
+            } else {
+                log.info("NextObj for {} does not exist. Skip updateL2Lb", newL2Lb.l2LbId());
+            }
         });
     }
 
-    private NextObjective.Builder nextObjBuilder(L2LbId l2LbId, Set<PortNumber> ports) {
-        return nextObjBuilder(l2LbId, ports, flowObjService.allocateNextId());
-    }
-
-    private NextObjective.Builder nextObjBuilder(L2LbId l2LbId, Set<PortNumber> ports, int nextId) {
+    private NextObjective.Builder nextObjBuilder(L2LbId l2LbId, Set<PortNumber> ports, Integer nextId) {
+        if (nextId == null) {
+            nextId = flowObjService.allocateNextId();
+        }
         // TODO replace logical l2lb port
         TrafficSelector meta = DefaultTrafficSelector.builder()
                 .matchInPort(PortNumber.portNumber(l2LbId.key())).build();
@@ -314,6 +484,22 @@
         return nextObjBuilder;
     }
 
+    // Custom-built function, when the device is not available we need a fallback mechanism
+    private boolean isLocalLeader(DeviceId deviceId) {
+        if (!mastershipService.isLocalMaster(deviceId)) {
+            // When the device is available we just check the mastership
+            if (deviceService.isAvailable(deviceId)) {
+                return false;
+            }
+            // Fallback with Leadership service - device id is used as topic
+            NodeId leader = leadershipService.runForLeadership(
+                    deviceId.toString()).leaderNodeId();
+            // Verify if this node is the leader
+            return clusterService.getLocalNode().id().equals(leader);
+        }
+        return true;
+    }
+
     private final class L2LbObjectiveContext implements ObjectiveContext {
         private final L2LbId l2LbId;
 
@@ -324,13 +510,51 @@
         @Override
         public void onSuccess(Objective objective) {
             NextObjective nextObj = (NextObjective) objective;
-            log.debug("Added nextobj {} for L2 load balancer {}", nextObj, l2LbId);
+            log.debug("Success {} nextobj {} for L2 load balancer {}", nextObj.op(), nextObj, l2LbId);
+            // Operation done
+            L2LbData oldl2LbData = new L2LbData(l2LbId);
+            L2LbData newl2LbData = new L2LbData(l2LbId);
+            l2LbProvExecutor.execute(() -> {
+                // Other operations will not lead to a generation of an event
+                switch (nextObj.op()) {
+                    case ADD:
+                        newl2LbData.setNextId(nextObj.id());
+                        post(new L2LbEvent(L2LbEvent.Type.INSTALLED, newl2LbData, oldl2LbData));
+                        break;
+                    case REMOVE:
+                        oldl2LbData.setNextId(nextObj.id());
+                        post(new L2LbEvent(L2LbEvent.Type.UNINSTALLED, newl2LbData, oldl2LbData));
+                        break;
+                    default:
+                        break;
+                }
+            });
         }
 
         @Override
         public void onError(Objective objective, ObjectiveError error) {
             NextObjective nextObj = (NextObjective) objective;
-            log.debug("Failed to add nextobj {} for L2 load balancer {}", nextObj, l2LbId);
+            log.debug("Failed {} nextobj {} for L2 load balancer {} due to {}", nextObj.op(), nextObj,
+                      l2LbId, error);
+            l2LbProvExecutor.execute(() -> {
+                // Init the data structure
+                L2LbData l2LbData = new L2LbData(l2LbId);
+                // Update the next id and send the event;
+                switch (nextObj.op()) {
+                    case ADD:
+                        // If ADD is failing apps do not know the next id; let's update the store
+                        l2LbNextStore.remove(l2LbId);
+                        post(new L2LbEvent(L2LbEvent.Type.FAILED, l2LbData, l2LbData));
+                        break;
+                    case REMOVE:
+                        // If REMOVE is failing let's send also the info about the next id; no need to update the store
+                        l2LbData.setNextId(nextObj.id());
+                        post(new L2LbEvent(L2LbEvent.Type.FAILED, l2LbData, l2LbData));
+                        break;
+                    default:
+                        break;
+                }
+            });
         }
 
     }
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/L2LbAddCommand.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/L2LbAddCommand.java
index 4afa1fe..f3a593c 100644
--- a/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/L2LbAddCommand.java
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/L2LbAddCommand.java
@@ -20,7 +20,9 @@
 import org.apache.karaf.shell.api.action.Command;
 import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.l2lb.api.L2Lb;
 import org.onosproject.l2lb.api.L2LbAdminService;
+import org.onosproject.l2lb.api.L2LbId;
 import org.onosproject.l2lb.api.L2LbMode;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.PortNumber;
@@ -54,6 +56,10 @@
             required = true, multiValued = true)
     private String[] portsStr;
 
+    // Operation constants
+    private static final String CREATE = "Create";
+    private static final String UPDATE = "Update";
+
     @Override
     protected void doExecute() {
         DeviceId deviceId = DeviceId.deviceId(deviceIdStr);
@@ -64,7 +70,8 @@
                 .map(PortNumber::fromString).collect(Collectors.toSet());
 
         L2LbAdminService l2LbAdminService = get(L2LbAdminService.class);
-        l2LbAdminService.createOrUpdate(deviceId, l2LbPort, ports, mode);
+        L2Lb l2Lb = l2LbAdminService.createOrUpdate(deviceId, l2LbPort, ports, mode);
+        print("%s of %s executed", l2Lb == null ? CREATE : UPDATE, new L2LbId(deviceId, l2LbPort));
 
     }
 }
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/L2LbListCommand.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/L2LbListCommand.java
index e1504f5..d14a2d0 100644
--- a/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/L2LbListCommand.java
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/L2LbListCommand.java
@@ -18,6 +18,7 @@
 import org.apache.karaf.shell.api.action.Command;
 import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.core.ApplicationId;
 import org.onosproject.l2lb.api.L2Lb;
 import org.onosproject.l2lb.api.L2LbId;
 import org.onosproject.l2lb.api.L2LbService;
@@ -30,11 +31,19 @@
 @Service
 @Command(scope = "onos", name = "l2lbs", description = "Lists L2 load balancers")
 public class L2LbListCommand extends AbstractShellCommand {
+
+    // Operation constant
+    private static final String AVAILABLE = "Available";
+
     @Override
     public void doExecute() {
         L2LbService service = get(L2LbService.class);
+        // Get l2 load balancers and reservations
         Map<L2LbId, L2Lb> l2LbStore = service.getL2Lbs();
-
-        l2LbStore.forEach((l2LbId, l2Lb) -> print("%s -> %s, %s", l2LbId, l2Lb.ports(), l2Lb.mode()));
+        Map<L2LbId, ApplicationId> l2LbResStore = service.getReservations();
+        // Print id -> ports, mode, reservation
+        l2LbStore.forEach((l2LbId, l2Lb) -> print("%s -> %s, %s, %s", l2LbId, l2Lb.ports(), l2Lb.mode(),
+                                                  l2LbResStore.get(l2LbId) == null ?
+                                                          AVAILABLE : l2LbResStore.get(l2LbId).name()));
     }
 }
\ No newline at end of file
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/L2LbRemoveCommand.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/L2LbRemoveCommand.java
index 89c4bce..1f7b5c5 100644
--- a/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/L2LbRemoveCommand.java
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/L2LbRemoveCommand.java
@@ -19,7 +19,9 @@
 import org.apache.karaf.shell.api.action.Command;
 import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.l2lb.api.L2Lb;
 import org.onosproject.l2lb.api.L2LbAdminService;
+import org.onosproject.l2lb.api.L2LbId;
 import org.onosproject.net.DeviceId;
 
 /**
@@ -38,12 +40,17 @@
             required = true, multiValued = false)
     private String keyStr;
 
+    // Operation constants
+    private static final String EXECUTED = "Executed";
+    private static final String FAILED = "Failed";
+
     @Override
     protected void doExecute() {
         DeviceId deviceId = DeviceId.deviceId(deviceIdStr);
         int l2LbPort = Integer.parseInt(keyStr);
 
         L2LbAdminService l2LbAdminService = get(L2LbAdminService.class);
-        l2LbAdminService.remove(deviceId, l2LbPort);
+        L2Lb l2Lb = l2LbAdminService.remove(deviceId, l2LbPort);
+        print("Removal of %s %s", new L2LbId(deviceId, l2LbPort), l2Lb != null ? EXECUTED : FAILED);
     }
 }