Add explicit init/close methods to OpendaylightToaster
[controller.git] / opendaylight / md-sal / samples / toaster-provider / src / main / java / org / opendaylight / controller / sample / toaster / provider / OpendaylightToaster.java
index b4da5a3d226f05bfd02fe21890ea0307e7efb2ce..e475c320d1e11c45258ebb6415083589fcf0302d 100644 (file)
  */
 package org.opendaylight.controller.sample.toaster.provider;
 
-import java.util.Arrays;
-import java.util.Collections;
+import static org.opendaylight.controller.md.sal.binding.api.DataObjectModification.ModificationType.DELETE;
+import static org.opendaylight.controller.md.sal.binding.api.DataObjectModification.ModificationType.WRITE;
+import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.CONFIGURATION;
+import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.OPERATIONAL;
+import static org.opendaylight.yangtools.yang.common.RpcError.ErrorType.APPLICATION;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.AsyncFunction;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import java.util.Collection;
 import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.atomic.AtomicLong;
-
-import org.opendaylight.controller.config.yang.config.toaster_provider.impl.ToasterProviderRuntimeMXBean;
-import org.opendaylight.controller.md.sal.common.api.data.DataChangeEvent;
-import org.opendaylight.controller.sal.binding.api.NotificationProviderService;
-import org.opendaylight.controller.sal.binding.api.data.DataBrokerService;
-import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction;
-import org.opendaylight.controller.sal.common.util.RpcErrors;
-import org.opendaylight.controller.sal.common.util.Rpcs;
+import java.util.concurrent.atomic.AtomicReference;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
+import org.opendaylight.controller.md.sal.binding.api.NotificationPublishService;
+import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
+import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
+import org.opendaylight.controller.md.sal.common.api.data.OptimisticLockFailedException;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.md.sal.common.util.jmx.AbstractMXBean;
 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.DisplayString;
 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.MakeToastInput;
+import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.RestockToasterInput;
 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.Toaster;
 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.Toaster.ToasterStatus;
-import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.RestockToasterInput;
 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterBuilder;
 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterOutOfBreadBuilder;
 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterRestocked;
 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterRestockedBuilder;
 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterService;
-import org.opendaylight.controller.sal.binding.api.data.DataChangeListener;
-import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.toaster.app.config.rev160503.ToasterAppConfig;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.toaster.app.config.rev160503.ToasterAppConfigBuilder;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.opendaylight.yangtools.yang.common.RpcError;
+import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
 import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.util.concurrent.Futures;
-
-public class OpendaylightToaster implements ToasterService, ToasterProviderRuntimeMXBean,
-                                            DataChangeListener, AutoCloseable {
+public class OpendaylightToaster extends AbstractMXBean
+        implements ToasterService, ToasterProviderRuntimeMXBean, DataTreeChangeListener<Toaster>, AutoCloseable {
 
     private static final Logger LOG = LoggerFactory.getLogger(OpendaylightToaster.class);
 
-    public static final InstanceIdentifier<Toaster> TOASTER_IID = InstanceIdentifier.builder(Toaster.class).build();
-
+    private static final InstanceIdentifier<Toaster> TOASTER_IID = InstanceIdentifier.builder(Toaster.class).build();
     private static final DisplayString TOASTER_MANUFACTURER = new DisplayString("Opendaylight");
     private static final DisplayString TOASTER_MODEL_NUMBER = new DisplayString("Model 1 - Binding Aware");
 
-    private NotificationProviderService notificationProvider;
-    private DataBrokerService dataProvider;
+    private DataBroker dataBroker;
+    private NotificationPublishService notificationProvider;
+    private ListenerRegistration<OpendaylightToaster> dataTreeChangeListenerRegistration;
 
     private final ExecutorService executor;
 
-    // As you will see we are using multiple threads here. Therefore we need to be careful about concurrency.
-    // In this case we use the taskLock to provide synchronization for the current task.
-    private volatile Future<RpcResult<Void>> currentTask;
-    private final Object taskLock = new Object();
-
-    private final AtomicLong amountOfBreadInStock = new AtomicLong( 100 );
+    // This holds the Future for the current make toast task and is used to cancel the current toast.
+    private final AtomicReference<Future<?>> currentMakeToastTask = new AtomicReference<>();
 
+    // Thread safe holders
+    private final AtomicLong amountOfBreadInStock = new AtomicLong(100);
     private final AtomicLong toastsMade = new AtomicLong(0);
+    private final AtomicLong darknessFactor = new AtomicLong(1000);
 
-    // Thread safe holder for our darkness multiplier.
-    private final AtomicLong darknessFactor = new AtomicLong( 1000 );
+    private final ToasterAppConfig toasterAppConfig;
 
     public OpendaylightToaster() {
+        this(new ToasterAppConfigBuilder().setManufacturer(TOASTER_MANUFACTURER).setModelNumber(TOASTER_MODEL_NUMBER)
+                .setMaxMakeToastTries(2).build());
+    }
+
+    public OpendaylightToaster(ToasterAppConfig toasterAppConfig) {
+        super("OpendaylightToaster", "toaster-provider", null);
         executor = Executors.newFixedThreadPool(1);
+        this.toasterAppConfig = toasterAppConfig;
     }
 
-    public void setNotificationProvider(NotificationProviderService salService) {
-        this.notificationProvider = salService;
+    public void setNotificationProvider(final NotificationPublishService notificationPublishService) {
+        this.notificationProvider = notificationPublishService;
     }
 
-    public void setDataProvider(DataBrokerService salDataProvider) {
-        this.dataProvider = salDataProvider;
-        updateStatus();
+    public void setDataBroker(final DataBroker dataBroker) {
+        this.dataBroker = dataBroker;
+    }
+
+    public void init() {
+        LOG.info("Initializing...");
+
+        Preconditions.checkNotNull(dataBroker, "dataBroker must be set");
+        dataTreeChangeListenerRegistration = dataBroker.registerDataTreeChangeListener(
+                new DataTreeIdentifier<>(CONFIGURATION, TOASTER_IID), this);
+        setToasterStatusUp(null);
+
+        // Register our MXBean.
+        register();
     }
 
     /**
      * Implemented from the AutoCloseable interface.
      */
     @Override
-    public void close() throws ExecutionException, InterruptedException {
+    public void close() {
+        LOG.info("Closing...");
+
+        // Unregister our MXBean.
+        unregister();
+
         // When we close this service we need to shutdown our executor!
         executor.shutdown();
 
-        if (dataProvider != null) {
-            final DataModificationTransaction t = dataProvider.beginTransaction();
-            t.removeOperationalData(TOASTER_IID);
-            t.commit().get();
+        if (dataTreeChangeListenerRegistration != null) {
+            dataTreeChangeListenerRegistration.close();
         }
-    }
 
-    private Toaster buildToaster() {
-        // We don't need to synchronize on currentTask here b/c it's declared volatile and
-        // we're just doing a read.
-        boolean isUp = currentTask == null;
+        if (dataBroker != null) {
+            WriteTransaction tx = dataBroker.newWriteOnlyTransaction();
+            tx.delete(OPERATIONAL,TOASTER_IID);
+            Futures.addCallback(tx.submit(), new FutureCallback<Void>() {
+                @Override
+                public void onSuccess(final Void result) {
+                    LOG.debug("Successfully deleted the operational Toaster");
+                }
+
+                @Override
+                public void onFailure(final Throwable failure) {
+                    LOG.error("Delete of the operational Toaster failed", failure);
+                }
+            });
+        }
+    }
 
+    private Toaster buildToaster(final ToasterStatus status) {
         // note - we are simulating a device whose manufacture and model are
         // fixed (embedded) into the hardware.
         // This is why the manufacture and model number are hardcoded.
-        ToasterBuilder tb = new ToasterBuilder();
-        tb.setToasterManufacturer(TOASTER_MANUFACTURER).setToasterModelNumber(TOASTER_MODEL_NUMBER)
-                .setToasterStatus(isUp ? ToasterStatus.Up : ToasterStatus.Down);
-        return tb.build();
+        return new ToasterBuilder().setToasterManufacturer(toasterAppConfig.getManufacturer())
+                .setToasterModelNumber(toasterAppConfig.getModelNumber()).setToasterStatus(status).build();
     }
 
     /**
-     * Implemented from the DataChangeListener interface.
+     * Implemented from the DataTreeChangeListener interface.
      */
     @Override
-    public void onDataChanged( DataChangeEvent<InstanceIdentifier<?>, DataObject> change ) {
-        DataObject dataObject = change.getUpdatedConfigurationData().get( TOASTER_IID );
-        if( dataObject instanceof Toaster )
-        {
-            Toaster toaster = (Toaster) dataObject;
-            Long darkness = toaster.getDarknessFactor();
-            if( darkness != null )
-            {
-                darknessFactor.set( darkness );
+    public void onDataTreeChanged(Collection<DataTreeModification<Toaster>> changes) {
+        for (DataTreeModification<Toaster> change: changes) {
+            DataObjectModification<Toaster> rootNode = change.getRootNode();
+            if (rootNode.getModificationType() == WRITE) {
+                Toaster oldToaster = rootNode.getDataBefore();
+                Toaster newToaster = rootNode.getDataAfter();
+                LOG.info("onDataTreeChanged - Toaster config with path {} was added or replaced: "
+                        + "old Toaster: {}, new Toaster: {}", change.getRootPath().getRootIdentifier(),
+                        oldToaster, newToaster);
+
+                Long darkness = newToaster.getDarknessFactor();
+                if (darkness != null) {
+                    darknessFactor.set(darkness);
+                }
+            } else if (rootNode.getModificationType() == DELETE) {
+                LOG.info("onDataTreeChanged - Toaster config with path {} was deleted: old Toaster: {}",
+                        change.getRootPath().getRootIdentifier(), rootNode.getDataBefore());
             }
         }
     }
 
     /**
-     * RestConf RPC call implemented from the ToasterService interface.
+     * RPC call implemented from the ToasterService interface that cancels the current toast, if any.
      */
     @Override
     public Future<RpcResult<Void>> cancelToast() {
-        synchronized (taskLock) {
-            if (currentTask != null) {
-                currentTask.cancel(true);
-                currentTask = null;
-            }
+        Future<?> current = currentMakeToastTask.getAndSet(null);
+        if (current != null) {
+            current.cancel(true);
         }
-        // Always return success from the cancel toast call.
-        return Futures.immediateFuture(Rpcs.<Void> getRpcResult(true, Collections.<RpcError> emptySet()));
+
+        // Always return success from the cancel toast call
+        return Futures.immediateFuture(RpcResultBuilder.<Void>success().build());
     }
 
     /**
-     * RestConf RPC call implemented from the ToasterService interface.
+     * RPC call implemented from the ToasterService interface that attempts to make toast.
      */
     @Override
-    public Future<RpcResult<Void>> makeToast(MakeToastInput input) {
+    public Future<RpcResult<Void>> makeToast(final MakeToastInput input) {
         LOG.info("makeToast: " + input);
 
-        synchronized (taskLock) {
-            if (currentTask != null) {
-                // return an error since we are already toasting some toast.
-                LOG.info( "Toaster is already making toast" );
+        final SettableFuture<RpcResult<Void>> futureResult = SettableFuture.create();
 
-                RpcResult<Void> result = Rpcs.<Void> getRpcResult(false, null, Arrays.asList(
-                        RpcErrors.getRpcError( null, null, null, null,
-                                               "Toaster is busy", null, null ) ) );
-                return Futures.immediateFuture(result);
-            }
-            else if( outOfBread() ) {
-                RpcResult<Void> result = Rpcs.<Void> getRpcResult(false, null, Arrays.asList(
-                        RpcErrors.getRpcError( null, null, null, null,
-                                               "Toaster is out of bread", null, null ) ) );
-                return Futures.immediateFuture(result);
-            }
-            else {
-                // Notice that we are moving the actual call to another thread,
-                // allowing this thread to return immediately.
-                // The MD-SAL design encourages asynchronus programming. If the
-                // caller needs to block until the call is
-                // complete then they can leverage the blocking methods on the
-                // Future interface.
-                currentTask = executor.submit(new MakeToastTask(input));
+        checkStatusAndMakeToast(input, futureResult, toasterAppConfig.getMaxMakeToastTries());
+
+        return futureResult;
+    }
+
+    private RpcError makeToasterOutOfBreadError() {
+        return RpcResultBuilder.newError(APPLICATION, "resource-denied", "Toaster is out of bread", "out-of-stock",
+                null, null);
+    }
+
+    private RpcError makeToasterInUseError() {
+        return RpcResultBuilder.newWarning(APPLICATION, "in-use", "Toaster is busy", null, null, null);
+    }
+
+    private void checkStatusAndMakeToast(final MakeToastInput input, final SettableFuture<RpcResult<Void>> futureResult,
+            final int tries) {
+        // Read the ToasterStatus and, if currently Up, try to write the status to Down.
+        // If that succeeds, then we essentially have an exclusive lock and can proceed
+        // to make toast.
+        final ReadWriteTransaction tx = dataBroker.newReadWriteTransaction();
+        ListenableFuture<Optional<Toaster>> readFuture = tx.read(OPERATIONAL, TOASTER_IID);
+
+        final ListenableFuture<Void> commitFuture =
+            Futures.transform(readFuture, (AsyncFunction<Optional<Toaster>, Void>) toasterData -> {
+                ToasterStatus toasterStatus = ToasterStatus.Up;
+                if (toasterData.isPresent()) {
+                    toasterStatus = toasterData.get().getToasterStatus();
+                }
+
+                LOG.debug("Read toaster status: {}", toasterStatus);
+
+                if (toasterStatus == ToasterStatus.Up) {
+
+                    if (outOfBread()) {
+                        LOG.debug("Toaster is out of bread");
+                        return Futures.immediateFailedCheckedFuture(
+                                new TransactionCommitFailedException("", makeToasterOutOfBreadError()));
+                    }
+
+                    LOG.debug("Setting Toaster status to Down");
+
+                    // We're not currently making toast - try to update the status to Down
+                    // to indicate we're going to make toast. This acts as a lock to prevent
+                    // concurrent toasting.
+                    tx.put(OPERATIONAL, TOASTER_IID, buildToaster(ToasterStatus.Down));
+                    return tx.submit();
+                }
+
+                LOG.debug("Oops - already making toast!");
+
+                // Return an error since we are already making toast. This will get
+                // propagated to the commitFuture below which will interpret the null
+                // TransactionStatus in the RpcResult as an error condition.
+                return Futures.immediateFailedCheckedFuture(
+                        new TransactionCommitFailedException("", makeToasterInUseError()));
+            });
+
+        Futures.addCallback(commitFuture, new FutureCallback<Void>() {
+            @Override
+            public void onSuccess(final Void result) {
+                // OK to make toast
+                currentMakeToastTask.set(executor.submit(new MakeToastTask(input, futureResult)));
             }
-        }
 
-        updateStatus();
-        return currentTask;
+            @Override
+            public void onFailure(final Throwable ex) {
+                if (ex instanceof OptimisticLockFailedException) {
+
+                    // Another thread is likely trying to make toast simultaneously and updated the
+                    // status before us. Try reading the status again - if another make toast is
+                    // now in progress, we should get ToasterStatus.Down and fail.
+
+                    if (tries - 1 > 0) {
+                        LOG.debug("Got OptimisticLockFailedException - trying again");
+                        checkStatusAndMakeToast(input, futureResult, tries - 1);
+                    } else {
+                        futureResult.set(RpcResultBuilder.<Void>failed()
+                                .withError(ErrorType.APPLICATION, ex.getMessage()).build());
+                    }
+                } else if (ex instanceof TransactionCommitFailedException) {
+                    LOG.debug("Failed to commit Toaster status", ex);
+
+                    // Probably already making toast.
+                    futureResult.set(RpcResultBuilder.<Void>failed()
+                            .withRpcErrors(((TransactionCommitFailedException)ex).getErrorList()).build());
+                } else {
+                    LOG.debug("Unexpected error committing Toaster status", ex);
+                    futureResult.set(RpcResultBuilder.<Void>failed().withError(ErrorType.APPLICATION,
+                            "Unexpected error committing Toaster status", ex).build());
+                }
+            }
+        });
     }
 
     /**
@@ -188,20 +306,18 @@ public class OpendaylightToaster implements ToasterService, ToasterProviderRunti
      * ToasterRestocked notification.
      */
     @Override
-    public Future<RpcResult<java.lang.Void>> restockToaster(RestockToasterInput input) {
-        LOG.info( "restockToaster: " + input );
+    public Future<RpcResult<java.lang.Void>> restockToaster(final RestockToasterInput input) {
+        LOG.info("restockToaster: " + input);
 
-        synchronized( taskLock ) {
-            amountOfBreadInStock.set( input.getAmountOfBreadToStock() );
+        amountOfBreadInStock.set(input.getAmountOfBreadToStock());
 
-            if( amountOfBreadInStock.get() > 0 ) {
-                ToasterRestocked reStockedNotification =
-                    new ToasterRestockedBuilder().setAmountOfBread( input.getAmountOfBreadToStock() ).build();
-                notificationProvider.publish( reStockedNotification );
-            }
+        if (amountOfBreadInStock.get() > 0) {
+            ToasterRestocked reStockedNotification = new ToasterRestockedBuilder()
+                    .setAmountOfBread(input.getAmountOfBreadToStock()).build();
+            notificationProvider.offerNotification(reStockedNotification);
         }
 
-        return Futures.immediateFuture(Rpcs.<Void> getRpcResult(true, Collections.<RpcError> emptySet()));
+        return Futures.immediateFuture(RpcResultBuilder.<Void>success().build());
     }
 
     /**
@@ -209,8 +325,8 @@ public class OpendaylightToaster implements ToasterService, ToasterProviderRunti
      */
     @Override
     public void clearToastsMade() {
-        LOG.info( "clearToastsMade" );
-        toastsMade.set( 0 );
+        LOG.info("clearToastsMade");
+        toastsMade.set(0);
     }
 
     /**
@@ -221,66 +337,79 @@ public class OpendaylightToaster implements ToasterService, ToasterProviderRunti
         return toastsMade.get();
     }
 
-    private void updateStatus() {
-        if (dataProvider != null) {
-            final DataModificationTransaction t = dataProvider.beginTransaction();
-            t.removeOperationalData(TOASTER_IID);
-            t.putOperationalData(TOASTER_IID, buildToaster());
+    private void setToasterStatusUp(final Function<Boolean,Void> resultCallback) {
+        WriteTransaction tx = dataBroker.newWriteOnlyTransaction();
+        tx.put(OPERATIONAL,TOASTER_IID, buildToaster(ToasterStatus.Up));
 
-            try {
-                t.commit().get();
-            } catch (InterruptedException | ExecutionException e) {
-                LOG.warn("Failed to update toaster status, operational otherwise", e);
+        Futures.addCallback(tx.submit(), new FutureCallback<Void>() {
+            @Override
+            public void onSuccess(final Void result) {
+                LOG.info("Successfully set ToasterStatus to Up");
+                notifyCallback(true);
             }
-        } else {
-            LOG.trace("No data provider configured, not updating status");
-        }
+
+            @Override
+            public void onFailure(final Throwable failure) {
+                // We shouldn't get an OptimisticLockFailedException (or any ex) as no
+                // other component should be updating the operational state.
+                LOG.error("Failed to update toaster status", failure);
+
+                notifyCallback(false);
+            }
+
+            void notifyCallback(final boolean result) {
+                if (resultCallback != null) {
+                    resultCallback.apply(result);
+                }
+            }
+        });
     }
 
-    private boolean outOfBread()
-    {
+    private boolean outOfBread() {
         return amountOfBreadInStock.get() == 0;
     }
 
-    private class MakeToastTask implements Callable<RpcResult<Void>> {
+    private class MakeToastTask implements Callable<Void> {
 
         final MakeToastInput toastRequest;
+        final SettableFuture<RpcResult<Void>> futureResult;
 
-        public MakeToastTask(MakeToastInput toast) {
-            toastRequest = toast;
+        MakeToastTask(final MakeToastInput toastRequest, final SettableFuture<RpcResult<Void>> futureResult) {
+            this.toastRequest = toastRequest;
+            this.futureResult = futureResult;
         }
 
         @Override
-        public RpcResult<Void> call() {
-            try
-            {
-                // make toast just sleeps for n secondn per doneness level.
-                long darknessFactor = OpendaylightToaster.this.darknessFactor.get();
-                Thread.sleep(darknessFactor * toastRequest.getToasterDoneness());
+        public Void call() {
+            try {
+                // make toast just sleeps for n seconds per doneness level.
+                Thread.sleep(OpendaylightToaster.this.darknessFactor.get() * toastRequest.getToasterDoneness());
 
-            }
-            catch( InterruptedException e ) {
-                LOG.info( "Interrupted while making the toast" );
+            } catch (InterruptedException e) {
+                LOG.info("Interrupted while making the toast");
             }
 
             toastsMade.incrementAndGet();
 
             amountOfBreadInStock.getAndDecrement();
-            if( outOfBread() ) {
-                LOG.info( "Toaster is out of bread!" );
-
-                notificationProvider.publish( new ToasterOutOfBreadBuilder().build() );
-            }
+            if (outOfBread()) {
+                LOG.info("Toaster is out of bread!");
 
-            synchronized (taskLock) {
-                currentTask = null;
+                notificationProvider.offerNotification(new ToasterOutOfBreadBuilder().build());
             }
 
-            updateStatus();
+            // Set the Toaster status back to up - this essentially releases the toasting lock.
+            // We can't clear the current toast task nor set the Future result until the
+            // update has been committed so we pass a callback to be notified on completion.
 
-            LOG.debug("Toast done");
+            setToasterStatusUp(result -> {
+                currentMakeToastTask.set(null);
+                LOG.debug("Toast done");
+                futureResult.set(RpcResultBuilder.<Void>success().build());
+                return null;
+            });
 
-            return Rpcs.<Void> getRpcResult(true, null, Collections.<RpcError> emptySet());
+            return null;
         }
     }
 }